From 3986ddd4fbf7f60c0f5b10e93474f4181bfe5f49 Mon Sep 17 00:00:00 2001 From: imdano <> Date: Fri, 29 Feb 2008 14:16:21 +0000 Subject: [PATCH] Fixed crash bug in script configuration dialog when a network doesn't have script options written in the config file yet. Refactored networking.py to not have to create a new wnettools interface every time a method gets called. Now it reuses the same one and makes changes to the iface name/driver as needed. Refactored a few methods in wnettools.py to be organized more logically and reduce external program calls. In experimental branch, added a few methods to networking/wnettools that can be used for enabling/disabling interfaces, as well as unloading/loading the driver associated with an interface. Added a check for mii-tool/ethtool that gets run when wicd starts, so it can decide which to use to check for a wired connection. Added a check for ip, to decide how to flush the routing tables. Rewrote some of the DHCP client checking code. Added a method (that's currently unused) to release a dhcp lease for each of the supported clients. --- configscript.py | 19 +++--- daemon.py | 3 +- misc.py | 15 ++--- networking.py | 132 ++++++++++++++++++++++++++------------ suspend.py | 2 + wicd.py | 2 +- wnettools.py | 164 ++++++++++++++++++++++++++++++++++++++++-------- 7 files changed, 257 insertions(+), 80 deletions(-) diff --git a/configscript.py b/configscript.py index b2c8ec7..3802361 100755 --- a/configscript.py +++ b/configscript.py @@ -82,6 +82,12 @@ def blank_to_none(text): return "None" else: return str(text) + +def get_val(con, network, val, default="None"): + if not con.has_option(network, val): + con.set(network, val, default) + return con.get(network, val) + def get_script_info(network, network_type): """ Read script info from disk and load it into the configuration dialog """ @@ -90,17 +96,16 @@ def get_script_info(network, network_type): if network_type == "wired": con.read(wired_conf) if con.has_section(network): - info["pre_entry"] = con.get(network, "beforescript") - info["post_entry"] = con.get(network, "afterscript") - info["disconnect_entry"] = con.get(network, "disconnectscript") + info["pre_entry"] = get_val(con, network, "beforescript") + info["post_entry"] = get_val(con, network, "afterscript") + info["disconnect_entry"] = get_val(con, network, "disconnectscript") else: bssid = wireless.GetWirelessProperty(int(network), "bssid") con.read(wireless_conf) if con.has_section(bssid): - info["pre_entry"] = con.get(bssid, "beforescript") - info["post_entry"] = con.get(bssid, "afterscript") - info["disconnect_entry"] = con.get(bssid, "disconnectscript") - + info["pre_entry"] = get_val(con, bssid, "beforescript") + info["post_entry"] = get_val(con, bssid, "afterscript") + info["disconnect_entry"] = get_val(con, bssid, "disconnectscript") return info def write_scripts(network, network_type, script_info): diff --git a/daemon.py b/daemon.py index f4418dc..9496695 100644 --- a/daemon.py +++ b/daemon.py @@ -45,6 +45,7 @@ import dbus import dbus.service if getattr(dbus, 'version', (0, 0, 0)) >= (0, 41, 0): import dbus.glib + # wicd specific libraries import wpath import networking @@ -176,7 +177,7 @@ class ConnectionWizard(dbus.service.Object): This number is major-minor-micro. Major is only incremented if minor reaches > 9. Minor is incremented if changes that break core stucture are implemented. Micro is for everything else, and micro may be - anything >= 0. This number is effective starting wicd v1.2.0 + anything >= 0. This number is effective starting wicd v1.2.0. """ version = '1.5.0' diff --git a/misc.py b/misc.py index 67e4c20..5043fca 100644 --- a/misc.py +++ b/misc.py @@ -167,8 +167,9 @@ def ParseEncryption(network): x = x.replace("$_" + str(t).upper(), str(network[t])) z = z + "\n" + x y += 1 - # Write the data to the files - # then chmod them so they can't be read by normal users + + # Write the data to the files then chmod them so they can't be read + # by normal users. file = open(wpath.networks + network["bssid"].replace(":", "").lower(), "w") os.chmod(wpath.networks + network["bssid"].replace(":", "").lower(), 0600) @@ -207,14 +208,14 @@ def LoadEncryptionMethods(): index = -1 for current in requiredFields: # The pretty names will start with an * so we can - # seperate them with that + # separate them with that. if current[0] == "*": # Make underscores spaces # and remove the * encryptionTypes[typeID][2][index][0] = current.replace("_", " ").lstrip("*") else: - # Add to the list of things that are required + # Add to the list of things that are required. index = len(encryptionTypes[typeID][2]) encryptionTypes[typeID][2][index] = {} encryptionTypes[typeID][2][index][1] = current @@ -252,7 +253,6 @@ def get_gettext(): _ = lang.gettext return _ - def to_unicode(x): """ Attempts to convert a string to unicode """ try: # This may never fail, but let's be safe @@ -270,12 +270,13 @@ def to_unicode(x): def error(parent, message): """ Shows an error dialog """ dialog = gtk.MessageDialog(parent, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, - gtk.BUTTONS_OK) + gtk.BUTTONS_OK) dialog.set_markup(message) dialog.run() dialog.destroy() -class LogWriter(): + +class LogWriter: """ A class to provide timestamped logging. """ def __init__(self): self.file = open(wpath.log + 'wicd.log','a') diff --git a/networking.py b/networking.py index a333f5c..c762a7b 100644 --- a/networking.py +++ b/networking.py @@ -36,9 +36,9 @@ class WiredConnectThread() -- Connection thread for wired # # -# Much thanks to wieman01 for help and support with various types of encyption +# Much thanks to wieman01 for help and support with various types of encyption. # Also thanks to foxy123, yopnono, and the many others who reported bugs helped -# and helped keep this project moving +# and helped keep this project moving. # import re @@ -63,12 +63,33 @@ class Controller(object): after_script = None disconnect_script = None driver = None + wiface = None + liface = None def __init__(self): """ Initialise the class. """ self.global_dns_1 = None self.global_dns_2 = None self.global_dns_3 = None + + def SetWiface(self, iface): + self.wiface.SetInterface(iface) + + def SetLiface(self, iface): + self.liface.SetInterface(iface) + + def __setattr__(self, attr, value): + if attr == 'wireless_interface': + object.__setattr__(self, attr, value) + if self.wiface: + self.SetWiface(value) + print 'hmm', self.wireless_interface + elif attr == 'wired_interface': + object.__setattr__(self, attr, value) + if self.liface: + self.SetLiface(value) + else: + object.__setattr__(self, attr, value) class ConnectThread(threading.Thread): @@ -168,6 +189,21 @@ class Wireless(Controller): """ Initialise the class. """ Controller.__init__(self) self.wpa_driver = None + self.wiface = wnettools.WirelessInterface(self.wireless_interface, + self.wpa_driver) + + def __setattr__(self, attr, value): + if attr == 'wpa_driver': + self.__dict__[attr] = value + if self.wiface: + self.SetWPADriver(value) + else: + object.__setattr__(self, attr, value) + + def LoadInterfaces(self): + """ Load the wnettools controls for the wired/wireless interfaces. """ + self.wiface = wnettools.WirelessInterface(self.wireless_interface, + self.wpa_driver) def Scan(self, essid=None): """ Scan for available wireless networks. @@ -179,8 +215,7 @@ class Wireless(Controller): A list of available networks sorted by strength. """ - wiface = wnettools.WirelessInterface(self.wireless_interface, - self.wpa_driver) + wiface = self.wiface # Prepare the interface for scanning wiface.Up() @@ -193,7 +228,7 @@ class Wireless(Controller): wiface.SetEssid(essid) aps = wiface.GetNetworks() - print aps + #print aps aps.sort(key=lambda x: x['strength']) return aps @@ -219,9 +254,7 @@ class Wireless(Controller): The current signal strength. """ - wiface = wnettools.WirelessInterface(self.wireless_interface, - self.wpa_driver) - return wiface.GetSignalStrength(iwconfig) + return self.wiface.GetSignalStrength(iwconfig) def GetDBMStrength(self, iwconfig=None): """ Get the dBm signal strength of the current network. @@ -230,9 +263,7 @@ class Wireless(Controller): The current dBm signal strength. """ - wiface = wnettools.WirelessInterface(self.wireless_interface, - self.wpa_driver) - return wiface.GetDBMStrength(iwconfig) + return self.wiface.GetDBMStrength(iwconfig) def GetCurrentNetwork(self, iwconfig=None): """ Get current network name. @@ -241,9 +272,7 @@ class Wireless(Controller): The name of the currently connected network. """ - wiface = wnettools.WirelessInterface(self.wireless_interface, - self.wpa_driver) - return wiface.GetCurrentNetwork(iwconfig) + return self.wiface.GetCurrentNetwork(iwconfig) def GetIP(self): """ Get the IP of the interface. @@ -252,15 +281,11 @@ class Wireless(Controller): The IP address of the interface in dotted notation. """ - wiface = wnettools.WirelessInterface(self.wireless_interface, - self.wpa_driver) - return wiface.GetIP() + return self.wiface.GetIP() def GetIwconfig(self): """ Get the out of iwconfig. """ - wiface = wnettools.WirelessInterface(self.wireless_interface, - self.wpa_driver) - return wiface.GetIwconfig() + return self.wiface.GetIwconfig() def CreateAdHocNetwork(self, essid, channel, ip, enctype, key, enc_used, ics): @@ -276,8 +301,7 @@ class Wireless(Controller): ics -- enable internet connection sharing """ - wiface = wnettools.WirelessInterface(self.wireless_interface, - self.wpa_driver) + wiface = self.wiface print 'Creating ad-hoc network' print 'Killing dhclient and wpa_supplicant' wiface.StopDHCP() @@ -288,7 +312,7 @@ class Wireless(Controller): wiface.SetMode('ad-hoc') wiface.SetChannel(channel) wiface.SetEssid(essid) - #Right now it just assumes you're using WEP + # Right now it just assumes you're using WEP if enc_used: print 'Setting encryption key' wiface.SetKey(key) @@ -332,14 +356,11 @@ class Wireless(Controller): return wnettools.GetWirelessInterfaces() def GetKillSwitchStatus(self): - wiface = wnettools.WirelessInterface(self.wireless_interface, - self.wpa_driver) - return wiface.GetKillSwitchStatus() + return self.wiface.GetKillSwitchStatus() def Disconnect(self): """ Disconnect from the network. """ - wiface = wnettools.WirelessInterface(self.wireless_interface, - self.wpa_driver) + wiface = self.wiface if self.disconnect_script != None: print 'Running wireless network disconnect script' misc.ExecuteScript(self.disconnect_script) @@ -347,6 +368,16 @@ class Wireless(Controller): wiface.SetAddress('0.0.0.0') wiface.Down() wiface.Up() + + def SetDriver(self): + self.driver = self.GetDriverName() + + def GetDriverName(self): + """ Gets the driver associated with the wireless interface. """ + return self.wiface.GetDriverName() + + def SetWPADriver(self, driver): + self.wiface.SetWpaDriver(driver) class WirelessConnectThread(ConnectThread): """ A thread class to perform the connection to a wireless network. @@ -425,10 +456,9 @@ class WirelessConnectThread(ConnectThread): wiface.SetAddress('0.0.0.0') liface.SetAddress('0.0.0.0') - print 'Stopping wpa_supplicant, dhclient, dhclient3' - wiface.StopDHCP() + print 'Stopping wpa_supplicant, and any running dhcp clients' wiface.StopWPA() - liface.StopDHCP() + wnettools.StopDHCP() if self.should_die: wiface.Up() @@ -561,6 +591,14 @@ class Wired(Controller): """ Initialise the class. """ Controller.__init__(self) self.wpa_driver = None + self.liface = wnettools.WiredInterface(self.wired_interface) + + def __setattr__(self, attr, val): + object.__setattr__(self, attr, val) + + def LoadInterfaces(self): + """ Load the wnettools controls for the wired/wireless interfaces. """ + self.liface = wnettools.WiredInterface(self.wired_interface) def CheckPluggedIn(self): """ Check whether the wired connection is plugged in. @@ -569,8 +607,7 @@ class Wired(Controller): The status of the physical connection link. """ - liface = wnettools.WiredInterface(self.wired_interface) - return liface.GetPluggedIn() + return self.liface.GetPluggedIn() def Connect(self, network): """ Spawn a connection thread to connect to the network. @@ -594,12 +631,11 @@ class Wired(Controller): The IP address of the interface in dotted notation. """ - liface = wnettools.WiredInterface(self.wired_interface) - return liface.GetIP() + return self.liface.GetIP() def Disconnect(self): """ Disconnect from the network. """ - liface = wnettools.WiredInterface(self.wired_interface) + liface = self.liface if self.disconnect_script != None: print 'Running wired disconnect script' misc.Run(self.disconnect_script) @@ -607,6 +643,25 @@ class Wired(Controller): liface.SetAddress('0.0.0.0') liface.Down() liface.Up() + + def SetDriver(self): + self.driver = self.GetDriverName() + + def GetDriverName(self): + """ Get the driver associated with the wired interface. """ + return self.liface.GetDriverName() + + def LoadDriver(self): + return self.liface.LoadDriver(self.driver) + + def UnloadDriver(self): + return self.liface.UnloadDriver(self.driver) + + def EnableInterface(self): + return self.liface.Up() + + def DisableInterface(self): + return self.liface.Down() class WiredConnectThread(ConnectThread): @@ -680,10 +735,9 @@ class WiredConnectThread(ConnectThread): wiface.SetAddress('0.0.0.0') liface.SetAddress('0.0.0.0') - print 'Stopping wpa_supplicant, dhclient, dhclient3' - wiface.StopDHCP() + print 'Stopping wpa_supplicant, and any dhcp clients' wiface.StopWPA() - liface.StopDHCP() + wnettools.StopDHCP() if self.should_die: liface.Up() diff --git a/suspend.py b/suspend.py index 8a2cef8..59271b7 100755 --- a/suspend.py +++ b/suspend.py @@ -32,5 +32,7 @@ proxy_obj = bus.get_object('org.wicd.daemon', '/org/wicd/daemon') daemon = dbus.Interface(proxy_obj, 'org.wicd.daemon') if __name__ == '__main__': + daemon.Disconnect() + daemon.SetForcedDisconnect(False) daemon.SetSuspend(True) diff --git a/wicd.py b/wicd.py index 1fe28fd..390dc24 100755 --- a/wicd.py +++ b/wicd.py @@ -376,7 +376,7 @@ class TrayIcon(): def usage(): """ Print usage information. """ print """ -wicd 1.40 +wicd 1.50 wireless (and wired) connection daemon front-end. Arguments: diff --git a/wnettools.py b/wnettools.py index e67e36d..b7677f9 100644 --- a/wnettools.py +++ b/wnettools.py @@ -99,6 +99,11 @@ def GetDefaultGateway(): gateway = None return gateway +def StopDHCP(): + """ Stop the DHCP client. """ + cmd = 'killall dhclient dhclient3 pump dhcpcd-bin' + misc.Run(cmd) + def GetWirelessInterfaces(): """ Get available wireless interfaces. @@ -122,38 +127,124 @@ class Interface(object): """ self.iface = iface self.verbose = verbose + self.Check() + + def SetInterface(self, iface): + """ Sets the interface. """ + self.iface = iface def CheckDHCP(self): """ Check that all required tools are available. """ # TODO: Implement this function. # THINGS TO CHECK FOR: ethtool, pptp-linux, dhclient, host - dhcpclients = ["dhclient", "dhcpcd", "pump -i"] + dhcpclients = ["dhclient", "dhcpcd", "pump"] for client in dhcpclients: - if misc.Run("which " + client.split()[0]): + if misc.Run("which " + client): DHCP_CLIENT = client break if not DHCP_CLIENT: - print "WARNING: NO DHCP CLIENT DETECTED!" + print "WARNING: NO DHCP CLIENT DETECTED! Make sure there is one \ + set in your path." + return + elif DHCP_CLIENT == "dhclient": + DHCP_CMD = "dhclient" + DHCP_RELEASE = "dhclient -r" + elif DHCP_CLIENT == "pump": + DHCP_CMD = "pump -i" + DHCP_RELEASE = "pump -r -i" + elif DHCP_CLIENT == "dhcpcd": + DHCP_CMD = "dhcpcd" + DHCP_RELEASE = "dhcpcd -r" + + self.DHCP_CMD = DHCP_CMD + self.DHCP_RELEASE = DHCP_RELEASE self.DHCP_CLIENT = DHCP_CLIENT + + def CheckWiredTools(self): + """ Check for the existence of ethtool and mii-tool. """ + if misc.Run("which mii-tool"): + self.MIITOOL_FOUND = True + else: + self.MIITOOL_FOUND = False + + if misc.Run("which ethtool"): + self.ETHTOOL_FOUND = True + else: + self.ETHTOOL_FOUND = False def Check(self): """ Check that all required tools are available.""" # TODO: Implement this function. # THINGS TO CHECK FOR: ethtool, pptp-linux, dhclient, host - pass + self.CheckDHCP() + self.CheckWiredTools() + + if misc.Run("which ip"): + self.IP_FOUND = True + else: + self.IP_FOUND = False def Up(self): """ Bring the network interface up. """ cmd = 'ifconfig ' + self.iface + ' up' if self.verbose: print cmd misc.Run(cmd) + return True def Down(self): """ Take down the network interface. """ cmd = 'ifconfig ' + self.iface + ' down' if self.verbose: print cmd misc.Run(cmd) + return True + + def GetDriverName(self): + """ Determine the driver name for the interface. + + Attempt to use ethtool to get the driver associated with a given + interface. If ethtool is not installed or ethtool fails to provide + a driver, None is returned. + + """ + if self.ETHTOOL_FOUND: + cmd = 'ethtool -i ' + self.iface + driver_pattern = re.compile('.*driver: (.*?)\n', re.I | re.M | + re.S) + driver_name = misc.RunRegex(driver_pattern, misc.Run(cmd)) + + if not driver_name or not self.ETHTOOL_FOUND: + print ("Could not determine driver name for iface " + self.iface + + " Is ethtool installed?") + return driver_name + + def LoadDriver(self, driver): + """ Enables the interface by loading the module given by driver. """ + if not driver: + print 'Error: No driver associated with this interface.' + return False + cmd = "modprobe " + driver + if self.verbose: print cmd + output = misc.Run(cmd, True, True) + out = output.readlines() + if out and out[0].startswith("FATAL"): + print "Could not enable Interface: " + out[0] + return False + return True + + def UnloadDriver(self, driver): + """ Disables the interface by removing the module given by driver """ + if not driver: + print 'Error: No driver associated with this interface.' + return False + cmd = "modprobe -r " + driver + if self.verbose: print cmd + output = misc.Run(cmd, True, True) + out = output.readlines() + if out and out[0].startswith("FATAL"): + print "Could not enable Interface: " + out[0] + return False + return True def SetAddress(self, ip=None, netmask=None, broadcast=None): """ Set the IP addresses of an interface. @@ -252,30 +343,30 @@ class Interface(object): return "dhcp_failed" def StartDHCP(self): - """ Start the DHCP client to obtain an IP address. """ - self.CheckDHCP() - DHCP_CLIENT = self.DHCP_CLIENT - - cmd = DHCP_CLIENT + " " + self.iface + """ Start the DHCP client to obtain an IP address. """ + cmd = self.DHCP_CMD + " " + self.iface if self.verbose: print cmd pipe = misc.Run(cmd, include_stderr=True, return_pipe=True) + DHCP_CLIENT = self.DHCP_CLIENT if DHCP_CLIENT == "dhclient": return self._parse_dhclient(pipe) - elif DHCP_CLIENT == "pump -i": + elif DHCP_CLIENT == "pump": return self._parse_pump(pipe) elif DHCP_CLIENT == "dhcpcd": return self._parse_dhcpcd(pipe) - - def StopDHCP(self): - """ Stop the DHCP client. """ - cmd = 'killall dhclient dhclient3 pump dhcpcd-bin' - if self.verbose: print cmd + + def ReleaseDHCP(self): + """ Release the DHCP lease for this interface. """ + cmd = self.DHCP_RELEASE + " " + self.iface misc.Run(cmd) def FlushRoutes(self): """ Flush all network routes. """ - cmd = 'ip route flush dev ' + self.iface + if self.IP_FOUND: + cmd = "ip route flush dev " + self.iface + else: + cmd = 'route del dev ' + self.iface if self.verbose: print cmd misc.Run(cmd) @@ -323,25 +414,44 @@ class WiredInterface(Interface): mii-tool will be used instead. """ + if self.ETHTOOL_FOUND: + return self._eth_get_plugged_in() + elif self.MIITOOL_FOUND: + return self._mii_get_plugged_in() + else: + print 'Error: No way of checking for a wired connection. Make \ + sure that either mii-tool or ethtool is installed.' + return False + + def _eth_get_plugged_in(self): link_tool = 'ethtool' if not self.IsUp(): print 'Wired Interface is down, putting it up' self.Up() time.sleep(6) tool_data = misc.Run(link_tool + ' ' + self.iface, True) - if misc.RunRegex(re.compile('(Operation not supported|\ - ethtool: command not found)', re.I), - tool_data) is not None: - print "ethtool check failed, falling back to mii-tool" - link_tool = 'mii-tool' - tool_data = misc.Run(link_tool + ' ' + self.iface, True) - - if misc.RunRegex(re.compile('(Link detected: yes|link ok)', - re.I | re.M | re.S), tool_data) is not None: + if misc.RunRegex(re.compile('(Link detected: yes)', re.I | re.M | + re.S), tool_data) is not None: return True else: return False + def _mii_get_plugged_in(self): + link_tool = 'mii-tool' + tool_data = misc.Run(link_tool + ' ' + self.iface, True) + if misc.RunRegex(re.compile('(Invalid argument)', re.I | re.M | re.S), + tool_data) is not None: + print 'Wired Interface is down, putting it up' + self.Up() + time.sleep(4) + tool_data = misc.Run(link_tool + ' ' + self.iface, True) + + if misc.RunRegex(re.compile('(link ok)', re.I | re.M | re.S), + tool_data) is not None: + return True + else: + return False + def IsUp(self): """ Determines if the interface is up. """ cmd = "ifconfig " + self.iface @@ -369,6 +479,10 @@ class WirelessInterface(Interface): """ Interface.__init__(self, iface, verbose) self.wpa_driver = wpa_driver + + def SetWpaDriver(self, driver): + """ Sets the wpa_driver. """ + self.wpa_driver = driver def SetEssid(self, essid): """ Set the essid of the wireless interface.