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;
};
1、加载好驱动并且开启了网卡
2、建立/var/run/文件夹
3、建立一个或者使用原有的配置文件
这个文件的前两行如下:
ctrl_interface=/var/run/wpa_supplicant
update_config=1
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