WPA_Supplicant使用及配置

WPA_Supplicant这个工具的下载和移植都很简单。一般来说,移植后能得到wpa_supplicant和wpa_cli这两个工具

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 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_supplicant
update_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_UDP
	int 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_UNIX
	int s;
	struct sockaddr_un local;
	struct sockaddr_un dest;
#endif /* CONFIG_CTRL_IFACE_UNIX */
#ifdef CONFIG_CTRL_IFACE_NAMED_PIPE
	HANDLE 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 ANDROID
	chmod(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

你可能感兴趣的:(Mips)