android的网络功能和一般的linux并无太大的区别,我原来以为在android上连接网络和普通的linux连接网络没有很大区别,事实上区别还是有一些的。
由于项目的需要,我的目标是在android的界面没有启动之前连接wifi,于是本来的期待是直接在init.rc中加入一些脚本调用即可,但研究了一会儿发现没有那么简单。
首先要感谢anly_jun@baidu贴吧的几篇博文,从http://hi.baidu.com/anly_jun/blog/item/8ecb92d593d144cf50da4b6e.html开始,一共有七篇关于android
wifi模块分析,其中有大量博主自己使用uml工具画的调用讲解,对于理解android wifi工作机制还是有很用的。其中最重要的是下面这几幅图。
(转自http://hi.baidu.com/anly_jun/blog/item/65e26e117e09ebcda6ef3f56.html)
在想要对wifi硬件动作之前,需要做两件事情,一是要load wifi的driver,而是要打开wpa_supplicant,其实如果是连接没有加密的wifi,没有必要打开wpa_supplicant,但是为了讲问题化为熟知的问题,此处还是先按照提示调用wifi_load_driver()和wifi_start_supplicant()
按照上面的提示写出来的初始化代码如下:
int init_stage() {
//
load the wifi driver: insmod .ko
int ret = wifi_load_driver();
if(ret < 0) {
loge("failed
to load wi-fi driver. %s",strerror(errno));
return -1;
}
start wpa_supplicant
ret = wifi_start_supplicant();
to start supplicant daemon. %s",strerror(errno));
return 0;
}
接下来,便是连接的过程了,经过上面的步骤,wifi的driver已经载入,wpa_supplicant也已经打开,那咱们就可以开始连接无线了吧。后来证实这是错误的,因为anly_jun的这篇android wifi分析的粒度只在java层面的函数级别,因此有一些细节并没有提到。在下面我会提到这些细节。
按照一般的linux中连接wifi的步骤,这时候就可以直接调用一个程序来连接某个ssid的无线网络,然后调用dhcpd来分配ip了,我之前在eeepc上连接wifi就非常简单,调用iwconfig [ssid],再调用dhcpd就可以了。但很遗憾,android上并没有iwconfig这样方便的工具。
这下线索似乎就断了,天无绝人之路,既然在android的java code中都可以添加一个无线网络并且连接,那我们就去android的java源代码中找一找。在android中,程序员是使用wifimanager这个类来进行wifi操作的,其中关于添加一个网络的代码如下:
/**
* add a new network description to the set of configured networks.
* the {@code networkid} field of the supplied configuration object
* is ignored.
* <p/>
* the new network will be marked disabled by default. to enable it,
* called {@link #enablenetwork}.
*
* @param config the set of variables that describe the configuration,
* contained in a {@link wificonfiguration} object.
* @return the id of the newly created network description. this is used in
* other operations to specified the network to be acted upon.
* returns {@code -1} on failure.
*/
public int addnetwork(wificonfiguration config) {
if (config == null) {
config.networkid = -1;
return addorupdatenetwork(config);
* internal method for doing the rpc that creates a new network description
* or updates an existing one.
* @param config the possibly sparse object containing the variables that
* are to set or updated in the network description.
* @return the id of the network on success, {@code -1} on failure.
private int addorupdatenetwork(wificonfiguration config) {
try {
return mservice.addorupdatenetwork(config);
} catch (remoteexception e) {
看其中确实是调用了service的函数,于是又在frameworks/base/services/java/com/android/server/wifiservice.java中找到了有关增加一个网络配置的相关代码
* see {@link android.net.wifi.wifimanager#addorupdatenetwork(wificonfiguration)}
* @return the supplicant-assigned identifier for the new or updated
* network if the operation succeeds, or {@code -1} if it fails
public int addorupdatenetwork(wificonfiguration config) {
enforcechangepermission();
/*
* if the supplied networkid is -1, we create a new empty
* network configuration. otherwise, the networkid should
* refer to an existing configuration.
*/
int netid = config.networkid;
boolean newnetwork = netid == -1;
boolean doreconfig = false;
// networkid of -1 means we want to create a new network
synchronized (mwifistatetracker) {
if (newnetwork) {
netid = mwifistatetracker.addnetwork();
if (netid < 0) {
if (dbg) {
slog.d(tag, "failed to add a network!");
}
return -1;
}
doreconfig = true;
}
mneedreconfig = mneedreconfig || doreconfig;
setvariables: {
/*
* note that if a networkid for a non-existent network
* was supplied, then the first setnetworkvariable()
* will fail, so we don't bother to make a separate check
* for the validity of the id up front.
*/
if (config.ssid != null &&
!mwifistatetracker.setnetworkvariable(
netid,
wificonfiguration.ssidvarname,
config.ssid)) {
if (dbg) {
slog.d(tag, "failed to set ssid: "+config.ssid);
break setvariables;
if (config.bssid != null &&
wificonfiguration.bssidvarname,
config.bssid)) {
slog.d(tag, "failed to set bssid: "+config.bssid);
}
我们来到frameworks/base/wifi/java/android/net/wifi/wifistatetracker.java中,看看addnetwork和setnetworkvariable函数都是什么样的。
* add a network
* @return network id of the new network
public synchronized int addnetwork() {
if (mwifistate.get() != wifi_state_enabled) {
return wifinative.addnetworkcommand();
* set network setting by name
* @param netid network id of the network
* @param name network variable key
* @param value network variable value
* @return {@code true} if the operation succeeds, {@code false} otherwise
public synchronized boolean setnetworkvariable(int netid,string name, string value) {
return false;
return wifinative.setnetworkvariablecommand(netid, name,value);
似乎wifinative就应该是最终boss了,于是来到frameworks/base/wifi/java/android/net/wifi/wifinative.java中一看,这两个函数都是这么定义的。
public native static int addnetworkcommand();
public native static boolean setnetworkvariablecommand(intnetid, string name, string value);
熟悉android ndk开发的朋友都应该知道,这里的函数都是通过jni链接到c/c++代码中,于是我们稍微花了一点时间,找到了这个函数所对应的frameworks/base/core/jni/android_net_wifi_wifi.cpp
static int docommand(const char *cmd, char *replybuf, intreplybuflen)
{
size_t reply_len = replybuflen - 1;
if (::wifi_command(cmd, replybuf, &reply_len) != 0)
else {
// strip off trailing newline
if (reply_len > 0 && replybuf[reply_len-1] == '\n')
replybuf[reply_len-1] = '\0';
else
replybuf[reply_len] = '\0';
return 0;
static jint dointcommand(const char *cmd)
char reply[256];
if (docommand(cmd, reply, sizeof(reply)) != 0) {
return (jint)-1;
} else {
return (jint)atoi(reply);
static jboolean dobooleancommand(const char *cmd, const char*expect)
return (jboolean)jni_false;
return (jboolean)(strcmp(reply, expect) == 0);
……
static jint android_net_wifi_addnetworkcommand(jnienv* env,jobject clazz)
return dointcommand("add_network");
static jbooleanandroid_net_wifi_setnetworkvariablecommand(jnienv* env,
jobject clazz,
jint netid,
jstring name,
jstring value)
char cmdstr[256];
jboolean iscopy;
const char *namestr = env->getstringutfchars(name,&iscopy);
const char *valuestr = env->getstringutfchars(value,&iscopy);
if (namestr == null || valuestr == null)
return jni_false;
int cmdtoolong = snprintf(cmdstr, sizeof(cmdstr),"set_network %d %s %s",
netid, namestr, valuestr) >=(int)sizeof(cmdstr);
env->releasestringutfchars(name, namestr);
env->releasestringutfchars(value, valuestr);
return (jboolean)!cmdtoolong && dobooleancommand(cmdstr,"ok");
// ----------------------------------------------------------------------------
/*
* jni registration.
static jninativemethod gwifimethods[] = {
/* name, signature, funcptr */
{ "loaddriver", "()z", (void*)android_net_wifi_loaddriver },
{ "unloaddriver", "()z", (void*)android_net_wifi_unloaddriver },
{ "startsupplicant", "()z", (void*)android_net_wifi_startsupplicant },
{ "stopsupplicant", "()z", (void*)android_net_wifi_stopsupplicant },
{ "connecttosupplicant", "()z", (void*)android_net_wifi_connecttosupplicant },
{ "closesupplicantconnection", "()v", (void*)android_net_wifi_closesupplicantconnection },
{ "listnetworkscommand", "()ljava/lang/string;",
(void*) android_net_wifi_listnetworkscommand },
{ "addnetworkcommand", "()i", (void*)android_net_wifi_addnetworkcommand },
{ "setnetworkvariablecommand", "(iljava/lang/string;ljava/lang/string;)z",
(void*) android_net_wifi_setnetworkvariablecommand},
{ "getnetworkvariablecommand", "(iljava/lang/string;)ljava/lang/string;",
(void*) android_net_wifi_getnetworkvariablecommand},
看来这下终于找到干活儿的函数了,可喜可贺,于是我们得出结论:在android中,要连上一个无线网络,首先需要添加一个网络,然后设置这个网络的配置,而这些步骤实际上都是通过向wpa_supplicant发送命令实现的。命令的格式例外的很简单,比如添加一个网络配置是add_network,设置一个网络配置是set_network [netid] [varname] [var]。于是根据java代码我写出了配置环节的代码。
int config_stage(){
// add a network config to supplicant mode
int networkid = dointcommand("add_network"); // add a new network id
if(networkid < 0) {
loge("failed to add a network configuration. %s",strerror(errno));
loge("add a network %d",networkid);
// set the ssid of the destination wifi adhoc
snprintf(cmdstr, sizeof(cmdstr), "set_network %d %s %s",networkid, ssid_name, ssid);
if(!dobooleancommand(cmdstr,"ok")) {
loge("failed to set network %d configuration ssid. %s", networkid, strerror(errno));
return networkid;
添加了一个配置的网络之后,选择某个网络配置的命令是select_network [netid]。在select_network之后,wpa_supplicant就尝试和该ssid的ap进行associate操作,在associate之后便调用dhcpd获取ip。select_network是立即返回的函数,那么何以得知已经和无线ap连接上了呢?android的界面里面连接/断开wifi都是可以被接受到的事件,在wifistatetracker中我们看到是由wifimonitor来监听由wpa_supplicant发过来的消息的,于是我们来到framework/base/wifi/java/android/net/wifi/wifimonitor.java,看到其中接受消息的monitor线程
class monitorthread extends thread {
public monitorthread() {
super("wifimonitor");
public void run() {
if (connecttosupplicant()) {
// send a message indicating that it is now possible to send commands
// to the supplicant
mwifistatetracker.notifysupplicantconnection();
} else {
mwifistatetracker.notifysupplicantlost();
return;
//noinspection infiniteloopstatement
for (;;) {
string eventstr = wifinative.waitforevent();
// skip logging the common but mostly uninteresting scan-results event
if (config.logd &&eventstr.indexof(scanresultsevent) == -1) {
log.v(tag, "event [" + eventstr + "]");
if (!eventstr.startswith(eventprefix)) {
if (eventstr.startswith(wpaeventprefix) &&
0 <eventstr.indexof(passwordkeymaybeincorrectevent)) {
handlepasswordkeymaybeincorrect();
continue;
string eventname =eventstr.substring(eventprefixlen);
int nameend = eventname.indexof(' ');
if (nameend != -1)
eventname = eventname.substring(0, nameend);
if (eventname.length() == 0) {
if (config.logd) log.i(tag, "received wpa_supplicant event with empty event
name");
wifinative.wairforevent在wifi.c中对应的函数是wifi_wait_event(),用来阻塞的等待wpa_supplicant发来的消息。当wpa_supplicant连接上之后,便会发出一个control-event-connected的消息,只要截获这个消息,我们便可以进行dhcp的操作了。
基于以上的考虑,我写了connect阶段的代码。这里我偷了一下懒,没有仔细的去分析过来的命令,只是匹配到有connected的命令,就当已经连接上了。
#define connected "connected"
int connect_stage(int networkid) {
// enable the network
snprintf(cmdstr, sizeof(cmdstr), "select_network
%d",networkid);
loge("failed to select network %d. %s", networkid, strerror(errno));
// wait for connect
char buf[256];
while(1) {
int nread = wifi_wait_for_event(buf, sizeof(buf));
if(nread > 0) {
loge("receive buf:\n %s\n",buf);
if(strstr(buf,connected) > 0) {
break;
// xxx danger of not going out of the loop!!!
continue;
}
在wifistatetracer中我还找到了负责dhcp的dhcphandler,按照其中的函数写了dhcp阶段的代码
int dhcp_stage(){
int result;
in_addr_t ipaddr, gateway, mask, dns1, dns2, server;
uint32_t lease;
char ifname[256];
char mdns1name[256];
char mdns2name[256];
property_get("wifi.interface", ifname ,"eth0");
snprintf(mdns1name, sizeof(mdns1name), "net.%s.dns1",ifname);
snprintf(mdns2name, sizeof(mdns2name), "net.%s.dns2",ifname);
result = dhcp_do_request(ifname, &ipaddr, &gateway, &mask, &dns1,&dns2, &server, &lease);
if(result != 0) {
to dhcp on interface %s. %s", ifname, strerror(errno));
struct in_addr dns_struct1, dns_struct2;
dns_struct1.s_addr = dns1;
dns_struct2.s_addr = dns2;
property_set(mdns1name,inet_ntoa(dns_struct1));
property_set(mdns2name,inet_ntoa(dns_struct2));
于是我用无线路由器建立了一个最简单的无线网络,ssid=tsever,没有密码,按照上面步骤进行联网。结果在add_network的步骤就出错了,无法获得网络id,这是怎么一回事呢?看到dhcp时的代码要对特定的network interface做操作,突然想起来之前在代码中似乎看到有对interface做使能操作的代码——wifiservice在调用mwifistatetracker.enablenetwork之前,还调用了string ifname = mwifistatetracker.getinterfacename();
networkutils.enableinterface(ifname);这两句。于是在init阶段,加上了下面这段。
ret = ifc_enable(ifname);
loge("failed to enable wifi interface %s. %s", ifname ,strerror(errno));
}
但是运行起来还是出同样的错误,不过这次busybox ifconfig之后可以发现eth0的端口了 ,至少没有做无用功。倒回去看在wifimonitor的monitorthread中有connecttosupplicant()的调用,本以为此调用不是非常关键,然后去hardware/libhardware_legacy/wifi/wifi.c文件里面看到全局有一个static struct wpa_ctrl *ctrl_conn;并且在wifi_command中需要用到这个ctrl_conn,那么如果这个ctrl_conn是null的话,所有的wifi_command命令自然都无法完成了,于是又加上了
ret = wifi_connect_to_supplicant();
to connect supplicant daemon. %s",strerror(errno));
这样编译完运行之后,果然顺利的分配到id了。运行一下程序,还是出现了错误,输出的log如下。
i/wpa_supplicant( 618): ctrl-event-scan-results ready
i/wpa_supplicant( 618): trying to associate with 00:24:b2:c1:16:b6 (ssid='tserver' freq=2462 mhz)
i/wpa_supplicant( 618): ctrl-event-state-change id=-1 state=3
e/ ( 612): finished init stage.
e/ ( 612): add a network 2
e/ ( 612): finished config stage.
i/wpa_supplicant( 618): ctrl-event-state-change id=1 state=0
e/ ( 612): receive buf:
e/ ( 612): ctrl-event-state-change id=1 state=0
i/wpa_supplicant( 618): ctrl-event-state-change id=-1 state=2
e/ ( 612): ctrl-event-state-change id=-1 state=2
e/ ( 612): ctrl-event-scan-results ready
i/wpa_supplicant( 618): ctrl-event-state-change id=-1 state=4
e/ ( 612): ctrl-event-state-change id=-1 state=4
i/wpa_supplicant( 618): associated with 00:24:b2:c1:16:b6
e/ ( 612): associated with 00:24:b2:c1:16:b6
d/networklocationprovider( 127): oncelllocationchanged [4517,30785]
i/wpa_supplicant( 618): authentication with 00:24:b2:c1:16:b6 timed out.
e/ ( 612): authentication with 00:24:b2:c1:16:b6 timed out.
注意到红色的那句log,我的wifi并没有设置密码,为什么还需要authentication呢?想一想是不是wpa_supplicant的限制,于是去网上搜了到了这篇wpa_supplicant
工具使用,发现没有密码的情况下应该是这样的配置
bash-4.1# wificonnect
i/wpa_supplicant( 871): ctrl-event-scan-results ready
i/wpa_supplicant( 871): trying to associate with 00:24:b2:c1:16:b6 (ssid='tserver' freq=2462 mhz)
i/wpa_supplicant( 871): ctrl-event-state-change id=-1 state=3
e/ ( 865): finished init stage.
e/ ( 865): add a network 2
e/ ( 865): finished config stage.
i/wpa_supplicant( 871): ctrl-event-state-change id=1 state=0
e/ ( 865): receive buf:
e/ ( 865): ctrl-event-state-change id=1 state=0
i/wpa_supplicant( 871): ctrl-event-state-change id=-1 state=2
e/ ( 865): ctrl-event-state-change id=-1 state=2
e/ ( 865): ctrl-event-scan-results ready
e/ ( 865): trying to associate with 00:24:b2:c1:16:b6 (ssid='tserver' freq=2462 mhz)
e/ ( 865): ctrl-event-state-change id=-1 state=3
i/wpa_supplicant( 871): ctrl-event-state-change id=2 state=4
e/ ( 865): ctrl-event-state-change id=2 state=4
i/wpa_supplicant( 871): associated with 00:24:b2:c1:16:b6
i/wpa_supplicant( 871): ctrl-event-state-change id=2 state=7
i/wpa_supplicant( 871): ctrl-event-connected - connection to 00:24:b2:c1:16:b6 completed (auth) [id=2 id_str=]
e/ ( 865): associated with 00:24:b2:c1:16:b6
e/ ( 865): ctrl-event-state-change id=2 state=7
e/ ( 865): ctrl-event-connected - connection to 00:24:b2:c1:16:b6 completed (auth) [id=2 id_str=]
e/ ( 865): finished dhcp stage.
snprintf(cmdstr, sizeof(cmdstr), "set_network %d %s %s",networkid, key_mgmt ,pass);
loge("failed to set network %d configuration key_mgmr. %s", networkid, strerror(errno));
snprintf(cmdstr, sizeof(cmdstr), "select_network %d",networkid);
loge("failed to select network %d. %s", networkid,strerror(errno));
snprintf(mdns1name, sizeof(mdns1name),"net.%s.dns1",ifname);
snprintf(mdns2name, sizeof(mdns2name),"net.%s.dns2",ifname);
result = dhcp_do_request(ifname, &ipaddr, &gateway,&mask, &dns1, &dns2, &server, &lease);
loge("failed to dhcp on interface %s. %s", ifname,strerror(errno));
int main(int argc, char *argv[])
int ret = init_stage();
loge("failed init stage. %s",strerror(errno));
exit(-1);
loge("finished init stage.");
ret = config_stage();
loge("failed config stage. %s",strerror(errno));
loge("finished config stage.");
ret = connect_stage(ret);
loge("failed connect stage. %s",strerror(errno));
logv("finished connect stage.");
ret = dhcp_stage();
loge("failed dhcp stage. %s",strerror(errno));
loge("finished dhcp stage.");