ap_hostapd_node.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
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 # range for valid txpower search
00018 MIN_TXPOWER = 0                 # dBm
00019 MAX_TXPOWER = 30                # dBm
00020 
00021 IFNAMSIZ = 16                   # interface name size
00022 MAX_SSID_LENGTH = 32
00023 
00024 # ioctl calls from wireless.h
00025 SIOCGIWESSID = 0x8B1B           # get ESSID
00026 SIOCSIWRATE = 0x8B20            # set default bit rate (bps)
00027 SIOCGIWFREQ = 0x8B05            # get channel/frequency (Hz)
00028 SIOCSIWTXPOW = 0x8B26           # set transmit power (dBm)
00029 SIOCGIWTXPOW = 0x8B27           # get transmit power (dBm)
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         # create hostapd.conf
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         # save bitrate setting
00277         if 'bitrate' in self.config:
00278             old_bitrate = self.config['bitrate']
00279 
00280         # update with new parameters
00281         self.config.update(config)                
00282 
00283         # if any hostapd parameter changed, stop hostapd
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         # tx-power
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         # bitrate
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()


hostapd_access_point
Author(s): Catalin Drula
autogenerated on Thu Apr 24 2014 15:35:09