ddwrt_apcontrol_node.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 
00003 import urllib, urllib2, base64, httplib
00004 import string, math, re, time
00005 
00006 import roslib; roslib.load_manifest('ddwrt_access_point')
00007 import rospy
00008 
00009 import dynamic_reconfigure.server
00010 from access_point_control.cfg import ApControlConfig
00011 from ieee80211_channels.channels import IEEE80211_Channels
00012 
00013 MAX_HTTP_REQUEST_TRIES=10
00014 INTERVAL_BETWEEN_TRIES=1.0 # seconds
00015 
00016 class IncompleteResponseBody(Exception):
00017     def __str__(self):
00018         return "Could not find </html> in response body"
00019 
00020 class DdwrtApControl:
00021 
00022     def __init__(self, hostname, username, password, interface = "wl0"):
00023         self.hostname = hostname
00024         self.username = username
00025         self.password = password
00026         self.interface = interface
00027         if interface == "wl0":
00028             self.other_interface = "wl1"
00029         else:
00030             self.other_interface = "wl0"
00031 
00032         self.passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
00033         self.passmgr.add_password(realm=None,
00034                                   uri="http://%s/"%(hostname),
00035                                   user=username,
00036                                   passwd=password)
00037         self.auth_handler = urllib2.HTTPBasicAuthHandler(self.passmgr)
00038         self.opener = urllib2.build_opener(self.auth_handler)
00039         urllib2.install_opener(self.opener)
00040 
00041         # dd-wrt WRT610n specific config
00042         if self.interface == "wl0":
00043             self.band = IEEE80211_Channels.BAND_2400_MHz
00044         else:
00045             self.band = IEEE80211_Channels.BAND_5000_MHz
00046         self.min_txpower_mw = 1
00047         self.max_txpower_mw = 251
00048 
00049         self.current_config = {}
00050         self.get_current_config()
00051 
00052         node_name = rospy.get_name()
00053         for param_name in self.current_config:
00054             param_full_name = node_name + "/" + param_name
00055             if not rospy.has_param(param_full_name):
00056                 rospy.set_param(param_full_name, self.current_config[param_name])
00057 
00058     def mw_to_dbm(self, mw):
00059         return int(10 * math.log10(float(mw)) + 0.5)
00060 
00061     def dbm_to_mw(self, dbm):
00062         return int(math.pow(10, float(dbm) / 10.0) + 0.5)
00063 
00064     def send_http_request(self, req):
00065         count = 0
00066         
00067         while True:
00068             count += 1
00069             try:
00070                 resp = urllib2.urlopen(req, timeout = 5.0)
00071                 body = resp.read()
00072                 if not re.findall("</html>", body):
00073                     raise IncompleteResponseBody()
00074                 break
00075             except (urllib2.URLError, httplib.BadStatusLine, IncompleteResponseBody), e:    
00076                 if count < MAX_HTTP_REQUEST_TRIES:
00077                     rospy.logwarn("HTTP request failed attempting again: %s", str(e))
00078                     time.sleep(INTERVAL_BETWEEN_TRIES)
00079                     continue
00080                 else:
00081                     raise e
00082 
00083         return body
00084 
00085     def apply_wrt_request(self, req_args, page):
00086         req = urllib2.Request("http://%s/apply.cgi"%(self.hostname))
00087 
00088         req.add_data(urllib.urlencode(req_args))
00089 
00090         auth = self.passmgr.find_user_password(None, "http://%s/apply.cgi"%(self.hostname))
00091         base64string = base64.encodestring("%s:%s" % auth)[:-1]
00092         req.add_header("Authorization", "Basic %s" % base64string)
00093         req.add_header("Referer", "http://%s/%s.asp"%(self.hostname, page))
00094 
00095         try:
00096             self.send_http_request(req)
00097         except (urllib2.URLError, httplib.BadStatusLine, IncompleteResponseBody), e:
00098             self.current_config['status'] = "FAIL"
00099             self.current_config['errmsg'] += "HTTP request failed: " + str(e)
00100 
00101     def get_page_info(self, page):        
00102         req = urllib2.Request("http://%s/%s.asp"%(self.hostname, page))
00103         auth = self.passmgr.find_user_password(None, "http://%s/%s.asp"%(self.hostname, page))
00104         base64string = base64.encodestring("%s:%s" % auth)[:-1]
00105         req.add_header("Authorization", "Basic %s" % base64string)
00106 
00107         return self.send_http_request(req)
00108 
00109     def find_ssid(self, interface, html):
00110         ssid = re.findall(r"<input[^>]*name=\"%s_ssid\"[^>]*value=[^>]*\"([^>]+?)\"[^>]*>"%(interface), html)
00111         if not ssid:
00112             return None
00113         return ssid[0]
00114 
00115     def find_mode(self, interface, html, mode):
00116         mode_block = re.findall(r"(?s)<select[^>]*name=\"%s_%s\"(.*?)</select>"%(interface, mode), html)
00117         if not mode_block:
00118             return None
00119         mode_block = mode_block[0]
00120         mode = re.findall(r"(?s)<option[^>]*value=[^\">]*?\"([^>]+?)[\\\"][^>]*?selected", mode_block)
00121         if not mode:
00122             return None
00123         return mode[0]
00124 
00125     def find_channel(self, interface, html):
00126         channel = re.findall(r"var %s_channel[^;]*?'(\d+)'"%(interface), html)
00127         if not channel:
00128             return None
00129         return int(channel[0])
00130 
00131     def find_bitrate(self, interface, html):
00132         bitrate_block = re.findall(r"(?s)<select[^>]*name=\"%s_rate\"(.*?)</select>"%(interface), html)
00133         if not bitrate_block:
00134             return None
00135         bitrate_block = bitrate_block[0]
00136         bitrate = re.findall(r"(?s)<option[^>]*value=[^\">]*?\"([^>]+?)[\\\"][^>]*?selected", bitrate_block)
00137         if not bitrate:
00138             return 0
00139         return int(bitrate[0])
00140 
00141     def find_txpower(self, interface, html):
00142         txpower = re.findall(r"<input[^>]*name=\"%s_txpwr\"[^>]*value=[^>]*\"([^>]+?)\"[^>]*>"%(interface), html)
00143         if not txpower:
00144             return None
00145         return int(txpower[0])
00146 
00147     def find_wmm(self, interface, html):
00148         wmm = re.findall(r"name=\"%s_wme\"[^>*]value=\"(.*?)\"[^>*]checked=\"checked\""%(self.interface), html)
00149         if not wmm:
00150             return None
00151         if wmm[0] == "on":
00152             return True
00153         elif wmm[0] == "off":
00154             return False
00155         else:
00156             return None
00157 
00158     def find_wep_key(self, interface, html):
00159         wep_key = re.findall(r"<input[^>]*name=[^>]*%s_key1[^>]*value=[^>]*\"([^>]*?)\"[^>]*>"%(interface), html)
00160         if not wep_key:
00161             return None
00162         return wep_key[0]
00163 
00164     def find_wpa_psk(self, interface, html):
00165         wpa_psk = re.findall(r"<input[^>]*name=[^>]*%s_wpa_psk[^>]*value=[^>]*\"([^>]*?)\"[^>]*>"%(interface), html)
00166         if not wpa_psk:
00167             return None
00168         return wpa_psk[0]
00169 
00170     def find_txpower(self, interface, html):
00171         txpower = re.findall(r"<input[^>]*name=\"%s_txpwr\"[^>]*value=[^>]*\"([^>]+?)\"[^>]*>"%(interface), html)
00172         if not txpower:
00173             return None
00174         return int(txpower[0])
00175 
00176     def get_other_interfaces_args(self):
00177         extra_args = {}
00178         html = self.get_page_info("Wireless_Basic")
00179         interface_list = re.findall(r"<select[^>]*name=[^>]*\"([a-zA-Z0-9]+)_mode\"[^>]*>", html)
00180 
00181         for interface in interface_list:
00182             if interface != self.interface:
00183                 ssid = self.find_ssid(interface, html)
00184                 mode = self.find_mode(interface, html, "mode")
00185                 if mode is None:
00186                     mode = "ap"
00187                 if ssid is not None:
00188                     extra_args["%s_mode"%(interface)] = mode
00189                     extra_args["%s_ssid"%(interface)] = ssid
00190 
00191         return extra_args
00192 
00193     def get_wireless_basic_params(self):
00194         # mode & ssid & channel
00195         html = self.get_page_info("Wireless_Basic")
00196         
00197         ssid = self.find_ssid(self.interface, html)
00198         mode = self.find_mode(self.interface, html, "net_mode")
00199         if mode is None:
00200             mode = "disabled"
00201         channel = self.find_channel(self.interface, html)
00202         
00203         return mode, ssid, channel
00204 
00205     def get_wireless_advanced_params(self):
00206         # bitrate & txpower & wmm
00207         html = self.get_page_info("Wireless_Advanced-%s"%(self.interface))
00208 
00209         bitrate = self.find_bitrate(self.interface, html)
00210         txpower = self.find_txpower(self.interface, html)
00211         wmm = self.find_wmm(self.interface, html)
00212         
00213         return bitrate, txpower, wmm
00214 
00215     def get_wireless_security_params(self):
00216         # encryption_mode | encryption_pass
00217         html = self.get_page_info("WL_WPATable")
00218 
00219         enc = self.find_mode(self.interface, html, "security_mode")
00220         if not enc:
00221             encryption_mode_out=['disabled']
00222         else:
00223             encryption_mode_out=[enc]
00224 
00225         if enc.find("psk") > -1:
00226             if enc == "psk":
00227                 enc_mode = ApControlConfig.ApControl_wpa
00228             elif enc == "psk2":
00229                 enc_mode = ApControlConfig.ApControl_wpa2
00230             elif enc == "psk psk2":
00231                 enc_mode = ApControlConfig.ApControl_wpa_wpa2
00232             else:
00233                 return None, ""
00234             enc_pass = self.find_wpa_psk(self.interface, html)
00235         elif enc == "wep":
00236             enc_mode = ApControlConfig.ApControl_wep
00237             enc_pass = self.find_wep_key(self.interface, html)
00238         elif enc == "disabled":
00239             enc_mode = ApControlConfig.ApControl_open
00240             enc_pass = ""
00241         else:
00242             return None, ""
00243 
00244         return enc_mode, enc_pass
00245 
00246     def get_current_config(self):
00247         mode, ssid, channel = self.get_wireless_basic_params()
00248         
00249         if mode is None:
00250             raise Exception("Could not read interface %s mode"%(self.interface))
00251         if mode != "disabled" and ssid is None:
00252             raise Exception("Could not read interface %s ssid"%(self.interface))
00253         if mode != "disabled" and channel is None:
00254             raise Exception("Could not read interface %s channel"%(self.interface))
00255 
00256         # enabled
00257         if mode == "disabled":
00258             self.current_config['enabled'] = False
00259         else:
00260             self.current_config['enabled'] = True
00261 
00262         # mode
00263         if mode in ["a-only", "b-only", "g-only"]:
00264             self.current_config['mode'] = mode[0]
00265             self.current_config['ieee80211n'] = False
00266         elif mode == "bg-mixed":
00267             self.current_config['mode'] = "g"
00268             self.current_config['ieee80211n'] = False
00269         elif mode == "mixed" and self.band == IEEE80211_Channels.BAND_2400_MHz:
00270             self.current_config['mode'] = "g"
00271             self.current_config['ieee80211n'] = True
00272         elif mode == "mixed" and self.band == IEEE80211_Channels.BAND_5000_MHz:
00273             self.current_config['mode'] = "a"
00274             self.current_config['ieee80211n'] = True
00275         elif mode == "n-only":
00276             self.current_config['ieee80211n'] = True
00277         else:
00278             self.current_config['mode'] = "unknown"
00279             self.current_config['ieee80211n'] = False
00280 
00281         #ssid
00282         self.current_config['ssid'] = ssid
00283 
00284         #freq
00285         self.current_config['freq'] = float(IEEE80211_Channels.get_freq(channel, self.band))
00286 
00287         bitrate, txpower_mw, wmm = self.get_wireless_advanced_params()
00288         if mode != "disabled" and (bitrate is None or txpower_mw is None or wmm is None):
00289             raise Exception("Could not read bitrate or txpower_mw or wmm: %s, %s, %s"%
00290                             (str(bitrate), str(txpower_mw), str(wmm)))
00291         
00292         #bitrate
00293         self.current_config['bitrate'] = bitrate
00294         #txpower
00295         self.current_config['txpower_auto'] = False
00296         self.current_config['txpower'] = self.mw_to_dbm(txpower_mw)
00297         #wmm
00298         self.current_config['wmm'] = wmm
00299 
00300         enc_mode, enc_pass = self.get_wireless_security_params()
00301         if mode != "disabled" and (enc_mode is None or enc_pass is None):
00302             raise Exception("Could not read encryption mode")
00303 
00304         self.current_config['encryption_mode'] = enc_mode 
00305         self.current_config['encryption_pass'] = enc_pass
00306 
00307     def set_wireless_basic(self, ssid, net_mode, channel):
00308         req_args = {}
00309         req_args["change_action"] = "gozila_cgi"
00310         req_args["submit_button"] = "Wireless_Basic"
00311         req_args["submit_type"] = "save"
00312         
00313         req_args["%s_mode"%(self.interface)] = "ap"
00314         req_args["%s_net_mode"%(self.interface)] = net_mode
00315         if ssid is not None:
00316             req_args["%s_ssid"%(self.interface)] = ssid
00317         if channel is not None:
00318             req_args["%s_channel"%(self.interface)] =  channel
00319         
00320         req_args.update(self.get_other_interfaces_args())
00321 
00322         #self.apply_wrt_request(req_args, "Wireless_Basic") # "Save"
00323         req_args["action"] = "ApplyTake" 
00324         self.apply_wrt_request(req_args, "Wireless_Basic") # "Apply Settings"
00325 
00326     def set_wireless_advanced(self, bitrate, txpower, wmm):
00327         req_args = {}
00328         req_args["change_action"] = ""  
00329         req_args["commit"] = "1"
00330         req_args["interface"] = self.interface
00331         req_args["submit_button"] = "Wireless_Advanced-%s"%(self.interface)
00332         req_args["submit_type"] = "save"
00333 
00334         req_args["%s_rate"%self.interface] = bitrate
00335         req_args["%s_txpwr"%(self.interface)] = txpower
00336         if wmm == True:
00337             req_args["%s_wme"%(self.interface)] = "on"
00338         elif wmm == False:
00339             req_args["%s_wme"%(self.interface)] = "off"
00340 
00341         self.apply_wrt_request(req_args, "Wireless_Advanced-%s") # "Save"
00342         req_args["action"] = "ApplyTake"
00343         self.apply_wrt_request(req_args, "Wireless_Advanced-%s"%(self.interface))
00344         
00345     def set_wireless_security(self, encryption_mode, encryption_passkey):
00346         req_args = {}
00347         req_args["action"] = "ApplyTake"
00348         req_args["change_action"] = "gozila_cgi"
00349         req_args["submit_button"] = "WL_WPATable"
00350         req_args["submit_type"] = "save"
00351 #        req_args["wl%d_security_mode"%(not int(self.interface[-1]))] = "disabled"
00352         if encryption_mode == ApControlConfig.ApControl_open:
00353             req_args["%s_security_mode"%(self.interface)] = "disabled"
00354         elif encryption_mode == ApControlConfig.ApControl_wep:
00355             req_args["%s_security_mode"%(self.interface)] = "wep"
00356             req_args["%s_WEP_key"%(self.interface)] = ""
00357             req_args["%s_key"%(self.interface)]= "1"
00358             req_args["%s_key1"%(self.interface)] = encryption_passkey
00359             req_args["%s_wep"%(self.interface)] = "restricted"
00360             req_args["%s_wep_bit"%(self.interface)] = "64" # supported: 64 or 128 bits
00361         elif encryption_mode in [ApControlConfig.ApControl_wpa,
00362                                  ApControlConfig.ApControl_wpa2,
00363                                  ApControlConfig.ApControl_wpa_wpa2]:
00364             if encryption_mode == ApControlConfig.ApControl_wpa:
00365                 req_args["%s_security_mode"%(self.interface)] = "psk"
00366             elif encryption_mode == ApControlConfig.ApControl_wpa2:
00367                 req_args["%s_security_mode"%(self.interface)] = "psk2"
00368             elif encryption_mode == ApControlConfig.ApControl_wpa_wpa2:
00369                 req_args["%s_security_mode"%(self.interface)] = "psk psk2"
00370             req_args["%s_crypto"%(self.interface)] = "tkip"
00371             req_args["%s_wpa_gtk_rekey"%(self.interface)] = "3600" 
00372             req_args["%s_wpa_psk"%(self.interface)] = encryption_passkey
00373         else:
00374             self.current_config['status'] = "FAIL"
00375             self.current_config['errmsg'] += "encryption mode %s not supported"%(encryption_mode)
00376             return
00377 
00378         #self.apply_wrt_request(req_args, "WL_WPATable") # "Save"
00379         #req_args["action"] = "ApplyTake" 
00380         self.apply_wrt_request(req_args, "WL_WPATable") # "Apply Settings"
00381 
00382     def compare_configs(self, requested_config, read_config):
00383         if requested_config['enabled'] != read_config['enabled']:
00384             self.current_config['status'] = "FAIL"
00385             self.current_config['errmsg'] += "Could not set enabled status, wrote %s, read %s"% \
00386                 (requested_config['enabled'], read_config['enabled'])
00387             return
00388 
00389         if read_config['enabled']:
00390             for prop in ['mode', 'ssid', 'freq', 'ieee80211n', 'txpower', 'bitrate', 'wmm', 'encryption_mode']:
00391                 if requested_config[prop] != read_config[prop]:
00392                     self.current_config['status'] = "FAIL"
00393                     self.current_config['errmsg'] += "Could not set %s, wrote %s, read %s"% \
00394                         (prop, str(requested_config[prop]), str(read_config[prop]))
00395 
00396             if read_config['encryption_mode'] != "open":
00397                 if requested_config['encryption_pass'] != read_config['encryption_pass']:
00398                     self.current_config['status'] = "FAIL"
00399                     self.current_config['errmsg'] += "Could not set encryption pass, wrote %s, read %s"% \
00400                         (requested_config['encryption_pass'], read_config['encryption_pass'])
00401 
00402     def reconfigure(self, config, level):
00403         self.current_config['status'] = "OK"
00404         self.current_config['errmsg'] = ""
00405 
00406         if config['enabled'] and \
00407                 ((config['mode'] == "a" and self.band != IEEE80211_Channels.BAND_5000_MHz) or \
00408                 ((config['mode'] == "b" or config['mode'] == "g") and self.band != IEEE80211_Channels.BAND_2400_MHz)):
00409             config['enabled'] = False
00410             self.current_config['status'] = "FAIL"
00411             self.current_config['errmsg'] = "Cannot set 802.11%s mode for interface in %s"%(config['mode'], self.band)
00412             
00413         change = False
00414         # enabled 
00415         if not config['enabled']:
00416             if self.current_config['enabled']:
00417                 self.set_wireless_basic(None, "disabled", None)
00418                 change = True
00419         else:
00420             # bitrate, txpower, wmm
00421             if config['txpower'] != self.current_config['txpower'] or \
00422                     config['bitrate'] != self.current_config['bitrate'] or \
00423                     config['wmm'] != self.current_config['wmm']:
00424 
00425                 new_txpower_mw = self.dbm_to_mw(config['txpower']) 
00426                 if new_txpower_mw > self.max_txpower_mw:
00427                     config['txpower'] = self.mw_to_dbm(self.max_txpower_mw)
00428                 if new_txpower_mw < self.min_txpower_mw:
00429                     config['txpower'] = self.mw_to_dbm(self.min_txpower_mw)
00430 
00431                 self.set_wireless_advanced(config['bitrate'], self.dbm_to_mw(config['txpower']), config['wmm'])
00432                 change = True
00433     
00434             # security params:
00435             if config['encryption_mode'] != self.current_config['encryption_mode'] or \
00436                     (config['encryption_mode'] != "open" and \
00437                          config['encryption_pass'] != self.current_config['encryption_pass']):
00438                 self.set_wireless_security(config['encryption_mode'], config['encryption_pass'])
00439                 change = True
00440 
00441             # ssid, freq, mode, ieee80211n
00442             if config['enabled'] != self.current_config['enabled'] or \
00443                     config['ssid'] != self.current_config['ssid'] or \
00444                     config['freq'] != self.current_config['freq'] or \
00445                     config['mode'] != self.current_config['mode'] or \
00446                     config['ieee80211n'] != self.current_config['ieee80211n']:
00447                 new_channel = IEEE80211_Channels.get_channel(config['freq'])
00448                 if not config['enabled']:
00449                     new_mode = "disabled"
00450                 elif config['ieee80211n']:
00451                     new_mode = "mixed"
00452                 else: 
00453                     new_mode = config['mode'] + "-only"
00454                     
00455                 self.set_wireless_basic(config['ssid'], new_mode, new_channel)
00456                 change = True
00457             
00458         # verify config
00459         if change:
00460             self.get_current_config()
00461             self.compare_configs(config, self.current_config) 
00462 
00463         if self.current_config['enabled']:
00464             return self.current_config
00465         else:
00466             config['status'] = self.current_config['status']
00467             config['errmsg'] = self.current_config['errmsg']
00468             config['enabled'] = False
00469             return config
00470 
00471 if __name__ == "__main__":
00472     rospy.init_node("ddwrt_apcontrol_node")
00473 
00474     ip = rospy.get_param("~ip", "192.168.1.1") 
00475     user = rospy.get_param("~user", "root") 
00476     password = rospy.get_param("~password", "admin") 
00477     interface = rospy.get_param("~interface", "wl0")
00478 
00479     ap = DdwrtApControl(ip, user, password, interface)
00480 
00481     dynamic_reconfigure.server.Server(ApControlConfig, ap.reconfigure)
00482 
00483     rospy.spin()


ddwrt_access_point
Author(s): Personal Networks
autogenerated on Thu Jan 2 2014 11:27:25