00001
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
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
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
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
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
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
00257 if mode == "disabled":
00258 self.current_config['enabled'] = False
00259 else:
00260 self.current_config['enabled'] = True
00261
00262
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
00282 self.current_config['ssid'] = ssid
00283
00284
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
00293 self.current_config['bitrate'] = bitrate
00294
00295 self.current_config['txpower_auto'] = False
00296 self.current_config['txpower'] = self.mw_to_dbm(txpower_mw)
00297
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
00323 req_args["action"] = "ApplyTake"
00324 self.apply_wrt_request(req_args, "Wireless_Basic")
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")
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
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"
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
00379
00380 self.apply_wrt_request(req_args, "WL_WPATable")
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
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
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
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
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
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()