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.");