00001
00002
00003 import time
00004 import subprocess
00005 from array import array
00006 import os, sys, signal
00007 import socket, fcntl
00008 import struct
00009 import math
00010
00011 import roslib; roslib.load_manifest('hostapd_access_point')
00012 import rospy
00013 import dynamic_reconfigure.server
00014 from access_point_control.cfg import ApControlConfig
00015 from ieee80211_channels.channels import IEEE80211_Channels
00016
00017
00018 MIN_TXPOWER = 0
00019 MAX_TXPOWER = 30
00020
00021 IFNAMSIZ = 16
00022 MAX_SSID_LENGTH = 32
00023
00024
00025 SIOCGIWESSID = 0x8B1B
00026 SIOCSIWRATE = 0x8B20
00027 SIOCGIWFREQ = 0x8B05
00028 SIOCSIWTXPOW = 0x8B26
00029 SIOCGIWTXPOW = 0x8B27
00030
00031 class ApHostapd:
00032 def __init__(self, interface, ip = None, netmask = None, hostapd = "hostapd"):
00033 self.hostapd = hostapd
00034 self.conffile = "/var/run/hostapd_" + interface + ".conf"
00035 self.pidfile = "/var/run/hostapd_" + interface + ".pid"
00036 self.dumpfile = "/var/run/hostapd_" + interface + ".dump"
00037 self.pid = None
00038
00039 self.interface = interface
00040 self.interface_data = array('c', interface + '\0' * (IFNAMSIZ-len(interface)));
00041 self.ioctl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
00042
00043 subprocess.call(["ifconfig", self.interface, "up"])
00044 if ip is not None:
00045 if netmask is not None:
00046 subprocess.call(["ifconfig", self.interface, ip, "netmask", netmask])
00047 else:
00048 subprocess.call(["ifconfig", self.interface, ip])
00049
00050 self.config = dict()
00051 self.paused = True
00052
00053 def isrunning(self):
00054 if self.pid is None:
00055 try:
00056 self.pid = self.get_hostapd_pid()
00057 except IOError as err:
00058 return False
00059 isrunning = os.path.exists("/proc/%d"%(self.pid))
00060 if not isrunning:
00061 self.pid = None
00062 return isrunning
00063
00064 def get_wpa_flag(self):
00065 if self.config['encryption_mode'] == ApControlConfig.ApControl_wpa:
00066 return 1
00067 if self.config['encryption_mode'] == ApControlConfig.ApControl_wpa2:
00068 return 2
00069 return 3
00070
00071 def generate_conf(self):
00072
00073 f = open(self.conffile, 'w')
00074
00075 channel = IEEE80211_Channels.get_channel(self.config['freq'])
00076
00077 if channel < 0:
00078 raise ValueError(-1, "Frequency not a valid IEEE 802.11 channel")
00079
00080 if (self.config['mode'] == 'a' and \
00081 IEEE80211_Channels.get_band_from_freq(self.config['freq']) != IEEE80211_Channels.BAND_5000_MHz) or \
00082 ((self.config['mode'] == 'b' or self.config['mode'] =='g') and \
00083 IEEE80211_Channels.get_band_from_freq(self.config['freq']) != IEEE80211_Channels.BAND_2400_MHz):
00084 raise ValueError("Requested frequency is not valid for 802.11" + self.config['mode'] + " mode")
00085
00086 f.write("""
00087 driver=nl80211
00088
00089 logger_syslog=-1
00090 logger_syslog_level=2
00091 logger_stdout=-1
00092 logger_stdout_level=0
00093
00094 ctrl_interface=/var/run/hostapd
00095
00096 ignore_broadcast_ssid=0
00097
00098 own_ip_addr=127.0.0.1
00099 country_code=""" + self.config['country_code'] + """
00100
00101 interface=""" + self.interface + """
00102 ssid=""" + self.config['ssid'] + """
00103 hw_mode=""" + self.config['mode'] + """
00104 channel=""" + str(channel) + """
00105 ieee80211n=""" + str(int(self.config['ieee80211n'])) + """
00106
00107 dump_file=""" + self.dumpfile)
00108
00109 if self.config['encryption_mode'] == ApControlConfig.ApControl_open:
00110 f.write("""
00111 auth_algs=1""")
00112 elif self.config['encryption_mode'] == ApControlConfig.ApControl_wep:
00113 f.write("""
00114 auth_algs=1
00115 wep_default_key=0
00116 wep_key0=""" + self.config['encryption_pass'])
00117 elif self.config['encryption_mode'] in \
00118 [ ApControlConfig.ApControl_wpa,
00119 ApControlConfig.ApControl_wpa2,
00120 ApControlConfig.ApControl_wpa_wpa2]:
00121 wpa_flag = self.get_wpa_flag()
00122 f.write("""
00123 auth_algs=3
00124 wpa=""" + str(wpa_flag) + """
00125 wpa_passphrase=""" + self.config['encryption_pass'] + """
00126 wpa_pairwise=CCMP TKIP""")
00127
00128 if self.config['wmm']:
00129 f.write("""
00130
00131 wmm_enabled=1
00132
00133 wmm_ac_bk_cwmax=10
00134 wmm_ac_bk_aifs=7
00135 wmm_ac_bk_txop_limit=0
00136 wmm_ac_bk_acm=0
00137 wmm_ac_be_aifs=3
00138 wmm_ac_be_txop_limit=0
00139 wmm_ac_be_acm=0
00140 wmm_ac_vi_aifs=2
00141 wmm_ac_vi_acm=0
00142 wmm_ac_vo_aifs=2
00143 wmm_ac_vo_acm=0
00144 """)
00145 if self.config['mode'] != "b":
00146 f.write("""
00147 wmm_ac_bk_cwmin=4
00148 wmm_ac_be_cwmin=4
00149 wmm_ac_be_cwmax=10
00150 wmm_ac_vi_cwmin=3
00151 wmm_ac_vi_cwmax=4
00152 wmm_ac_vi_txop_limit=94
00153 wmm_ac_vo_cwmin=2
00154 wmm_ac_vo_cwmax=3
00155 wmm_ac_vo_txop_limit=47
00156 """)
00157 else:
00158 f.write("""
00159 wmm_ac_bk_cwmin=5
00160 wmm_ac_be_cwmin=5
00161 wmm_ac_be_cwmax=7
00162 wmm_ac_vi_cwmin=4
00163 wmm_ac_vi_cwmax=5
00164 wmm_ac_vi_txop_limit=188
00165 wmm_ac_vo_cwmin=3
00166 wmm_ac_vo_cwmax=4
00167 wmm_ac_vo_txop_limit=102
00168 """)
00169 f.write("\n")
00170 f.close()
00171
00172 def restart_hostapd(self):
00173 self.stop_ap()
00174 self.start_ap()
00175
00176 def get_ioctl_param(self, ioctl_request):
00177 iwreq_data = array('c')
00178 iwreq_data.extend(self.interface_data)
00179 iwreq_data.extend(struct.pack("iBBH", 0, 0, 0, 0))
00180 iwreq_data.extend('\0' * (32 - len(iwreq_data)))
00181
00182 fcntl.ioctl(self.ioctl_sock.fileno(), ioctl_request, iwreq_data)
00183 return struct.unpack("iBBH", iwreq_data[16:24])
00184
00185 def start_ap(self):
00186 self.generate_conf()
00187 if self.isrunning():
00188 raise Exception("hostapd already running")
00189
00190 p = subprocess.Popen([self.hostapd, self.conffile, "-B", "-P", self.pidfile], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
00191 out, err = p.communicate()
00192 ret = p.wait()
00193 if ret != 0:
00194 print err, out
00195 raise Exception(ret, out)
00196
00197 self.paused = False
00198
00199 def get_hostapd_pid(self):
00200 f = open(self.pidfile, 'r')
00201 pid = int(f.read())
00202 f.close()
00203 return pid
00204
00205 def stop_ap(self):
00206 if self.isrunning():
00207 os.kill(self.pid, signal.SIGTERM)
00208 else:
00209 raise Exception("hostapd is dead")
00210
00211 signal_time = time.time()
00212 while self.isrunning() and time.time() - signal_time < 3.0:
00213 time.sleep(0.1)
00214
00215 if self.isrunning():
00216 rospy.logwarn("hostapd pid %d was not terminated by SIGTERM signal, wating some more...",
00217 self.pid)
00218 signal_time = time.time()
00219 while self.isrunning() and time.time() - signal_time < 7.0:
00220 time.sleep(0.1)
00221 if self.isrunning():
00222 os.kill(self.pid, signal.SIGKILL)
00223 rospy.logerr("hostapd pid %d terminated by SIGKILL signal", self.pid)
00224 raise Exception("could not kill hostapd")
00225
00226 self.paused = True
00227
00228 def get_ssid(self):
00229 ssid_buf = array('c', '\0' * MAX_SSID_LENGTH)
00230 ssid_buf_ptr, ssid_buf_len = ssid_buf.buffer_info()
00231 buf_ref = struct.pack('Pi', ssid_buf_ptr, ssid_buf_len)
00232
00233 iwreq_data = array('c')
00234 iwreq_data.extend(self.interface_data)
00235 iwreq_data.extend(buf_ref)
00236 iwreq_data.extend('\0' * (32 - len(iwreq_data)))
00237
00238 fcntl.ioctl(self.ioctl_sock.fileno(), SIOCGIWESSID, iwreq_data)
00239
00240 return ssid_buf.tostring()
00241
00242 def set_txpower(self, txpower, fixed = 1):
00243 iwreq_data = array('c')
00244 iwreq_data.extend(self.interface_data)
00245 iwreq_data.extend(struct.pack("iBBH", txpower, fixed, 0, 0))
00246 iwreq_data.extend('\0' * (32 - len(iwreq_data)))
00247
00248 fcntl.ioctl(self.ioctl_sock.fileno(), SIOCSIWTXPOW, iwreq_data)
00249
00250 def get_txpower(self):
00251 txpower, fixed, disable, flags = self.get_ioctl_param(SIOCGIWTXPOW)
00252 return txpower
00253
00254 def get_freq(self):
00255 iwreq_data = array('c')
00256 iwreq_data.extend(self.interface_data)
00257 iwreq_data.extend('\0' * (32 - len(iwreq_data)))
00258
00259 fcntl.ioctl(self.ioctl_sock.fileno(), SIOCGIWFREQ, iwreq_data)
00260 m, e = struct.unpack("ih", iwreq_data[16:22])
00261 return float(m) * math.pow(10, e)
00262
00263 def set_bitrate(self, bitrate, fixed = 1):
00264 iwreq_data = array('c')
00265 iwreq_data.extend(self.interface_data)
00266 iwreq_data.extend(struct.pack("iBBH", bitrate, fixed, 0, 0))
00267 iwreq_data.extend('\0' * (32 - len(iwreq_data)))
00268 status = fcntl.ioctl(self.ioctl_sock.fileno(), SIOCSIWRATE, iwreq_data)
00269
00270 def set_encryption(self, encryption_mode, encryption_pass):
00271 self.config['encryption_mode'] = encryption_mode
00272 self.config['encryption_pass'] = encryption_pass
00273 self.restart_hostapd()
00274
00275 def reconfigure(self, config, level):
00276
00277 if 'bitrate' in self.config:
00278 old_bitrate = self.config['bitrate']
00279
00280
00281 self.config.update(config)
00282
00283
00284 if 2 & level and not self.paused:
00285 self.stop_ap()
00286
00287 self.config['status'] = 'OK'
00288 self.config['errmsg'] = ''
00289
00290 if config['enabled'] and self.paused:
00291 try:
00292 self.start_ap()
00293 except Exception as e:
00294 self.config['enabled'] = False
00295 self.config['status'] = 'FAIL'
00296 self.config['errmsg'] = str(e)
00297
00298 if not config['enabled'] and not self.paused:
00299 self.stop_ap()
00300
00301
00302 if 4 & level:
00303 if config['txpower_auto']:
00304 self.set_txpower(-1, 0)
00305 self.config['txpower'] = self.get_txpower()
00306 else:
00307 txpower = config['txpower']
00308 txpower_is_set = False
00309 step = 0
00310 while not txpower_is_set and (txpower + step >= MIN_TXPOWER \
00311 or txpower + step <= MAX_TXPOWER):
00312 try:
00313 if txpower + step >= MIN_TXPOWER and txpower + step <= MAX_TXPOWER:
00314 self.set_txpower(txpower + step)
00315 else:
00316 raise Exception()
00317 txpower_is_set = True
00318 except:
00319 step = -step
00320 if step >= 0:
00321 step = step + 1
00322 if txpower_is_set:
00323 self.config['txpower'] = txpower + step
00324 else:
00325 self.config['status'] = 'FAIL'
00326 self.config['errmsg'] = self.config['errmsg'] + " Could not set a valid txpower within the search range"
00327 self.config['txpower'] = self.get_txpower()
00328
00329
00330 if 8 & level:
00331 try:
00332 if config['bitrate'] > 0:
00333 self.set_bitrate(config['bitrate'])
00334 else:
00335 self.set_bitrate(-1, 0)
00336 except Exception as err:
00337 self.config['status'] = 'FAIL'
00338 self.config['errmsg'] = self.config['errmsg'] + "\nCould not set bitrate\n" + str(err)
00339 self.config['bitrate'] = old_bitrate
00340
00341 return self.config
00342
00343
00344 if __name__ == "__main__":
00345 rospy.init_node("ap_hostapd_node")
00346
00347 interface = rospy.get_param("~interface")
00348 args = dict()
00349 try:
00350 args["ip"] = rospy.get_param("~ip")
00351 except KeyError:
00352 pass
00353 try:
00354 args["netmask"] = rospy.get_param("~netmask")
00355 except KeyError:
00356 pass
00357 try:
00358 args["hostapd"] = rospy.get_param("~hostapd_path")
00359 except KeyError:
00360 pass
00361
00362 ap = ApHostapd(interface, **args)
00363 if ap.isrunning():
00364 ap.stop_ap()
00365
00366 try:
00367 dynamic_reconfigure.server.Server(ApControlConfig, ap.reconfigure)
00368 rospy.spin()
00369 finally:
00370 if ap.isrunning():
00371 ap.stop_ap()