WPA_Supplicant使用及配置

来源:互联网 发布:qq分享组件js代码 编辑:程序博客网 时间:2024/05/03 13:23

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">WPA_Supplicant这个工具的下载和移植都很简单。一般来说,移植后能得到wpa_supplicant和wpa_cli这两个工具</span>

wpa_supplicant运行在后台,使用socket与前台进行数据通信

wpa_cli可以用来进行调试和查看wpa_supplicant的运行,能够帮助我们学习它的命令用法,以及了解返回数据的格式。


不过,我们最终的目的实际上是需要集成wpa_cli的发送接受功能,同时能够处理接收数据的程序。 可以参考里面的参考程序wpa_gui-qt4。

我的项目中对wifi的管理需要以下功能:

1、扫描wifi热点,每个热点需要显示名字和信号强度,必要时可显示加密方法

2、连接指定热点,支持WPA/WPA2/WEP常用加密方法,连接成功后分配IP

3、定期获取并更新wifi信号强度

4、支持wifi开启和关闭


将这些功能封装成wpa_controller的类:

class wpa_controller: public QObject{    Q_OBJECT    enum{        WPA_STARTED,        WPA_STOP    };public:        static wpa_controller * Instance()    {        static wpa_controller* _instance=NULL;        if(_instance==NULL)        {            _instance=new wpa_controller();                        JYZ_ASSERT(_instance);        }        return _instance;    }        static void ParseFlags(const QString& flag,int& auth,bool &encr);    bool IsInited() {return mInited;}    bool ImportConfigureFromFile(const char* filename);                         //从配置文件中导入网络配置参数    QString GetWIFIStatus();                                                    //查询网卡状态    QString GetConnectedSSID(){return mCurrentSSID;}                            //当前的SSID            void Scan();                                                                //发送扫描命令    bool AddNetWork(int auth,int enc,int& ,const char* ssid,const char* key);   //添加网络参数    void SelectNetwork(int id=-1);                                              //选择热点        bool RemoveNetWork(QString ssid);                                           //删除网络参数    bool BSS(int,QString&,QString&,QString&,QString&,QString&);                 //查询热点具体信息    void EnableNetworkInCfg(void);    bool Disconnect();                                                          //断开连接    //重启    void Reset();    //开启    bool Start();    //停止    void Stop();signals:    //wifi 扫描结果    void wifiScanResultUpdate();    //wifi 链接信息    void wifiConnected(bool connected,int reason,QString bssid);    //信号强度信息    void signallevelupdate(quint32 level);    private:        bool StopWpaSupplicant();    bool StartWpaSupplicant();    bool InstallDriver();                                                         //初始化    bool UninstallDriver();    void timerEvent(QTimerEvent *);        bool Ping();                                                                //测试连接,需要定时调用            void Reconfigure();                                                         //重新配置    void updateStatus();                                                        //更新链接状态        void updateNetworks();                                                      //更新连接列表    ~wpa_controller();        wpa_controller();        void EnableNetwork(int id=-1);          //使能网络        int ctrlRequest(const QString& cmd, QString&);    int setNetworkParam(int id, const char *field,const char *value, bool quote);        void processMsg(char *msg);    int openCtrlConnection();    private slots:    void receiveMsgs();        void resetWifi();    void startWifi();    void stopWifi();        public:    QMap<QString,int> WifiHotPotSavedList;    private:    QString mRequestReply;    QSocketNotifier *msgNotifier;    struct wpa_ctrl *ctrl_conn;    struct wpa_ctrl *monitor_conn;            bool    mWPSCapable;    //是否支持WPS    QString mWIFIStatus;    //WIFI当前状态    QString mIPAddress;     //当前IP地址    QString mBSSID;         //BSSID    QString mCurrentSSID;   //当前连接SSID    quint32 mSignalLevel;    QMutex  m_lock;    int     mTimerHandle;    bool    mDriverInstalled;    unsigned int mWifiFailCount;    bool    mInited;};


A 启动之前的必要条件:

1、加载好驱动并且开启了网卡

2、建立/var/run/文件夹

3、建立一个或者使用原有的配置文件

       这个文件的前两行如下:

ctrl_interface=/var/run/wpa_supplicantupdate_config=1

ctrl_interface指向的是一个目录,在这个目录中默认会生成一个文件/var/run/wpa_supplicant/wlan0,这是local socket address,用于我们的程序和后台程序wpa_supplicant进行通信(我们必须知道wpa_supplicant作为后台服务程序是通过本地socket和客户端进行通信的)

update_config为1时会在特定时候(客户端发送SAVE_CONFIG命令)更新这个配置文件。


B、启动

1、wpa_supplicant后台服务程序

      我的项目使用的是wpa_supplicant -Dwext -iwlan0 -c./wpa.conf -qq& 

2、openCtrlConnection()

      这个接口是演示程序自己封装的,它执行的主要过程是使用wpa_ctrl_open打开两次,获得两个wpa_ctrl变量。这些变量代表着后台服务程序wpa_supplicant,通过这两个变量可以和wpa_supplicant进行通信。不过他们的分工有点不同,一个变量用于客户端主动给服务程序发起命令,另外一个变量用于监控wpa_supplicant的是否有数据发给客户端程序。

     wpa_ctrl定义

     

struct wpa_ctrl {#ifdef CONFIG_CTRL_IFACE_UDPint s;struct sockaddr_in local;struct sockaddr_in dest;char *cookie;char *remote_ifname;char *remote_ip;#endif /* CONFIG_CTRL_IFACE_UDP */#ifdef CONFIG_CTRL_IFACE_UNIXint s;struct sockaddr_un local;struct sockaddr_un dest;#endif /* CONFIG_CTRL_IFACE_UNIX */#ifdef CONFIG_CTRL_IFACE_NAMED_PIPEHANDLE pipe;#endif /* CONFIG_CTRL_IFACE_NAMED_PIPE */};

     wpa_ctrl_open函数接口     

struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path){struct wpa_ctrl *ctrl;static int counter = 0;int ret;size_t res;int tries = 0;int flags;ctrl = os_malloc(sizeof(*ctrl));if (ctrl == NULL)return NULL;os_memset(ctrl, 0, sizeof(*ctrl));ctrl->s = socket(PF_UNIX, SOCK_DGRAM, 0);if (ctrl->s < 0) {os_free(ctrl);return NULL;}ctrl->local.sun_family = AF_UNIX;counter++;try_again:ret = os_snprintf(ctrl->local.sun_path, sizeof(ctrl->local.sun_path),  CONFIG_CTRL_IFACE_CLIENT_DIR "/"  CONFIG_CTRL_IFACE_CLIENT_PREFIX "%d-%d",  (int) getpid(), counter);if (ret < 0 || (size_t) ret >= sizeof(ctrl->local.sun_path)) {close(ctrl->s);os_free(ctrl);return NULL;}tries++;if (bind(ctrl->s, (struct sockaddr *) &ctrl->local,    sizeof(ctrl->local)) < 0) {if (errno == EADDRINUSE && tries < 2) {/* * getpid() returns unique identifier for this instance * of wpa_ctrl, so the existing socket file must have * been left by unclean termination of an earlier run. * Remove the file and try again. */unlink(ctrl->local.sun_path);goto try_again;}close(ctrl->s);os_free(ctrl);return NULL;}#ifdef ANDROIDchmod(ctrl->local.sun_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);chown(ctrl->local.sun_path, AID_SYSTEM, AID_WIFI);/* * If the ctrl_path isn't an absolute pathname, assume that * it's the name of a socket in the Android reserved namespace. * Otherwise, it's a normal UNIX domain socket appearing in the * filesystem. */if (ctrl_path != NULL && *ctrl_path != '/') {char buf[21];os_snprintf(buf, sizeof(buf), "wpa_%s", ctrl_path);if (socket_local_client_connect(    ctrl->s, buf,    ANDROID_SOCKET_NAMESPACE_RESERVED,    SOCK_DGRAM) < 0) {close(ctrl->s);unlink(ctrl->local.sun_path);os_free(ctrl);return NULL;}return ctrl;}#endif /* ANDROID */ctrl->dest.sun_family = AF_UNIX;res = os_strlcpy(ctrl->dest.sun_path, ctrl_path, sizeof(ctrl->dest.sun_path));if (res >= sizeof(ctrl->dest.sun_path)) {close(ctrl->s);os_free(ctrl);return NULL;}if (connect(ctrl->s, (struct sockaddr *) &ctrl->dest,    sizeof(ctrl->dest)) < 0) {close(ctrl->s);unlink(ctrl->local.sun_path);os_free(ctrl);return NULL;}/* * Make socket non-blocking so that we don't hang forever if * target dies unexpectedly. */flags = fcntl(ctrl->s, F_GETFL);if (flags >= 0) {flags |= O_NONBLOCK;if (fcntl(ctrl->s, F_SETFL, flags) < 0) {perror("fcntl(ctrl->s, O_NONBLOCK)");/* Not fatal, continue on.*/}}return ctrl;}

从上面可以看出打开的操作实际上就是给wpa_ctrl这个结构体赋值的过程,这个结构体最重要的元素s就是以后客户端程序和服务程序进行通讯的接口了。

3、客户端发送命令

      为了获取wifi设备的一些信息,客户端程序需要给服务程序发送命令。我的项目中使用到的命令包括:

      scan:发起扫描热点请求

      status:查询wifi设备状态。主要用于获得wifi信号强度以及链接指定热点时查看连接是否成功("wpa_state")

      list_networks:查询已经保存的热点信息,同时查看当前连接的热点名称

      ping:用于查看后台服务程序是否异常退出

      BSS %d:查看指定热点信息,如加密信息等

      ADD_NETWORK:添加热点,需要配合SET_NETWORK使用

      SAVE_CONFIG:保存热点信息更新到配置文件中(若update_config=1)

      RECONFIG:重新加载配置文件

      项目中,定时发送的命令包括PING和STATUS。 STATUS获得信号强度并显示,同时获取wifi设备状态(当状态从非Completed状态到Completed状态表明设备非连接状态变为连接状态)

4、侦听服务程序主动发送的数据

     openCtrlConnection时总共打开了两个wpa_ctrl,第一个用于客户端发送命令,第二个用于侦听服务程序主动发送给客户端程序的命令。如何侦听?使用QSocketNotifier。

      

msgNotifier = new QSocketNotifier(wpa_ctrl_get_fd(monitor_conn),QSocketNotifier::Read, this);    if(connect(msgNotifier, SIGNAL(activated(int)), SLOT(receiveMsgs()))==false)
或者新建一个线程使用select进行侦听。

     不管怎样,当发现有数据能读取时,读取socket并处理。

      为什么还需要侦听服务程序? 有些命令的回复是异步的,例如scan,扫描的结果并不是马上能得到。 也有一些wifi状态信息wifi设备连接或者断开变化。


扫描网络时:

1、发送SCAN命令

wpa_supplicant异步返回SCAN_RESULT事件

2、发送SCAN_RESULT命令得到扫描结果。(注意,必须等到SCAN_RESULT异步事件接受到后,SCAN_RESULT命令才能拿到正确的结果)

(SCAN_RESULT一次能得到所有的扫描结果,若想一次获得一个热点,可以使用BSS命令)


添加热点时:

1、通过扫描网络(BSS命令)能够得到指定热点的一些信息(SSID,安全策略,加密方式等)

2、根据安全策略和加密方式信息,设置加密密码

3、使用ADD_NETWORK添加这些信息到wpa_supplicant的配置文件中

此过程比较难理解的是第二步,不同的安全策略对于不同的加密方式对密码的要求不一样


ADD_NETWORK步骤

1、得到Network ID(若不知ID,可以发送ADD_NETWORK不带参数,将会得到ID)

设置 ssid、 auth_alg、proto、key_mgmt、 

  若支持WPA_PSK获知WPA2_PSK需要设置pairwise、 group、 psk

  若支持WEP需要设置wep_keyx和wep_txt_keyidx

 若支持EAP,需要设置eap、pcsc、phase1、pac_file、phase2(EAP挺复杂的,我的产品里没有支持他)

ENABLE_NETWORK

SAVE_CONFIG

SELECT_NETWORK


查询当前网卡的信息

1、通过STATUS命令能够得到当前链接状态(是否连接或断开,信号强度等)


查询当前网卡连接热点的信息

1、通过LIST_NETWORK,能够列出wpa_supplicant配置文件中所有保存热点的信息。

2、解析热点列表可以得到当前链接到的热点SSID(有current后缀)

不过例外情况是:

A 当使用错误密码连接一个热点时,LIST_NETWORK会将此热点当成已连接热点。

B 当使用正确密码连接热点,但分配不到IP时,LIST_NETWORK得到的信息也是有问题的。

所以,一般来说,还是STATUS命令更加有效,它能得到当前的网卡的状态是否为COMPLETED(代表密码正确,且连接成功),能得到IP_address(代表分配地址成功)


最后说说网络安全策略和加密方式的一些摘抄:

身份校验算法(WEP没有身份校验)两种:

802.1x + EAP 

Pre-shared Key

数据加密两种(类似WEP的RC4加密算法):

TKIP

AES

数据完整性编码校验两种(类似WEP的CRC32校验算法):

MIC

CCMP

0 0