最近一直在调试lollipop,翻译成中文好像是棒棒糖的意思,就是个wifi控制管理工具,比如设置DLNA或者WFD模式等,其原理是通过本地通信工具sockets控制其他接口来启动wpa_suplicant或者hostapd,从而实现功能,所以这里面涉及的还有wpa_supplicant工具包,以及netd,netd就是个服务器,接受ndc(客户端)的指令来执行动作,比如设置hostapd需要的参数,生成hostapd.conf文件,最后启动hostapd应用等。。
lollipop里面包含好几个bin工具
lollipop lollipop_ota lollipop_p2p lollipop_reset lollipop_softap
然后在init.rc里面配置了相应的服务,其中只有lollipop是开机启动的,其他几个服务都是由lollipop接收到指令来切换不同模式的时候去start对应的服务。
service lollipop /system/bin/lollipop disabled oneshot #class main service lollipop_p2p /system/bin/lollipop_p2p disabled oneshot service lollipop_softap /system/bin/lollipop_softap disabled oneshot service lollipop_ota /system/bin/lollipop_ota disabled oneshot service lollipop_reset /system/bin/lollipop_reset disabled oneshot
我的sdk是从andorid高度裁剪过的,砍掉了jni以上部分,只有c、c++部分以下,编译完成后img总共12m左右,烧录在16M的nor_flash的板子上。
拿到手的时候sdk中wpa_suplicant没有被包含编译,lollipop也没有源码包含,于是千辛万苦从别的sdk上挪了一个过来,加入各种mk文件中,还有各种依赖库,搞了两天终于编译完能在板子上跑起来了。
但是测试DLNA模式的手发现hostapd没有起来,所以根本不会有wifi热点出来。
查看了log,lollipop中没有打印什么错误信息。
要启动wifi热点一定要跑hostapd起来,有两种方式可以实现。
方式一:
在init.rc(或者类似xxx.rc)配置一个hostapd的service,然后在源代码中start这个服务
方式二:
直接在源代码中调用system或者用execl一类的函数来执行hostapd。
下面开始排查
第一步:
查看是否有配置调用hostapd的服务
果然在init.rc中看到一个hostapd服务,开机不运行,需要有人专门去start才行
service hostapd /system/bin/hostapd /data/misc/wifi/hostapd.conf
class main
disabled
oneshot
第二步
查看sdk中的源码,看下睡会去启动这个服务
第一个要找的就是lollipop部分,去里面cgrep一下
cgrep "hostapd" ./wifi/lollipop_softap.c:28:#define HOSTAPD_CONF_FILE "/data/misc/wifi/hostapd.conf" ./wifi/lollipop_softap.c:71: ALOGD("start hostapd ..."); ./wifi/lollipop_softap.c:72: if(execl("/system/bin/hostapd", "/system/bin/hostapd", ./wifi/lollipop_softap.c:155: "/data/misc/wifi/hostapd\nssid=%s\nchannel=6\nhw_mode=g\nieee80211n=1\n"HT_CAPAB, ./wifi/lollipop_softap.c:268: /* TODO: implement over hostapd */ ./socket_ipc/lollipop_socket_ipc.c:193: // chmod so hostapd which is wifi permission can send info to softap
这里没人启动hostapd服务,但是有人在调用hostapd执行档,进去
wifi/lollipop_softap.c
找到这个函数
int startSoftap(void)
50 int startSoftap(void) { 51 pid_t pid = 1; 52 int ret = 0; 53 54 if (mPid) { 55 ALOGE("Softap already started"); 56 return 0; 57 } 58 #if NEED_SOCK 59 if (mSock < 0) { 60 ALOGE("Softap startap - failed to open socket"); 61 return -1; 62 } 63 #endif 64 if ((pid = fork()) < 0) { 65 ALOGE("fork failed (%s)", strerror(errno)); 66 return -1; 67 } 68 69 if (!pid) { 70 ensure_entropy_file_exists(); 71 ALOGD("start rtl_hostapd ..."); 72 if(execl("/system/bin/rtl_hostapd", "/system/bin/rtl_hostapd", 73 "-e", WIFI_ENTROPY_FILE, 74 HOSTAPD_CONF_FILE, (char *) NULL)) { 75 ALOGE("execl failed (%s)", strerror(errno)); 76 } 77 ALOGE("Should never get here!"); 78 return -1; 79 } else { 80 mPid = pid; 81 ALOGD("Softap startap - Ok"); 82 usleep(AP_BSS_START_DELAY); 83 } 84 return ret; 85 86 }
接下来再去找谁会调用这个函数
cgrep "startSoftap" ./wifi/lollipop_softap.c:50:int startSoftap(void) { ./wifi/lollipop_softap.h:15:extern int startSoftap(void);
在lollipop里面是没人调用,croot回到android根目录查找
cgrep "startSoftap" ./system/netd/CommandListener.cpp:918: rc = sSoftapCtrl->startSoftap(); ./system/netd/SoftapController.cpp:54:int SoftapController::startSoftap() { ./system/netd/SoftapController.h:51: int startSoftap(); ./system/netd/SoftapController.h:58: virtual int startSoftap(); ./system/netd/SoftapController.h:85: int startSoftap(); ./system/netd/SoftapController.h:102: int startSoftap(); ./external/lollipop_wifi/wifi/lollipop_softap.c:50:int startSoftap(void) { ./external/lollipop_wifi/wifi/lollipop_softap.h:15:extern int startSoftap(void);
发现至少有两个地方有可能调用,最后进去跟踪源码,确认了一下,里面是有调用hostapd,但是不是调用了lollipop里面的startSoftap函数而是自己用execl执行了hostapd,
system/netd/SoftapController.cpp
int SoftapController::startSoftap() { 55 pid_t pid = 1; 56 57 if (mPid) { 58 ALOGE("SoftAP is already running"); 59 return ResponseCode::SoftapStatusResult; 60 } 61 62 if ((pid = fork()) < 0) { 63 ALOGE("fork failed (%s)", strerror(errno)); 64 return ResponseCode::ServiceStartFailed; 65 } 66 67 if (!pid) { 68 ensure_entropy_file_exists(); 69 if (execl(HOSTAPD_BIN_FILE, HOSTAPD_BIN_FILE, 70 "-e", WIFI_ENTROPY_FILE, 71 HOSTAPD_CONF_FILE, (char *) NULL)) { 72 ALOGE("execl failed (%s)", strerror(errno)); 73 } 74 ALOGE("SoftAP failed to start"); 75 return ResponseCode::ServiceStartFailed; 76 } else { 77 mPid = pid; 78 ALOGD("SoftAP started successfully"); 79 usleep(AP_BSS_START_DELAY); 80 } 81 return ResponseCode::SoftapStatusResult; 82 }
这部分接口是netd源码包里面的,netd也是一个wifi服务,可以通过ndc客户端给netd发送指令叫他干活,netd就可以实现wifi热点。
现在的线索又转移到了查找谁会调用ndc来发送指令,首先还是回去lollipop查找
cgrep "bin\/ndc" ./p2p_main.c:171: sprintf(cmd, "/system/bin/ndc softap fwreload %s STA", P2P_IFACE); ./softap_main.c:114: system("/system/bin/ndc ipfwd disable"); ./softap_main.c:115: system("/system/bin/ndc softap stopap"); ./softap_main.c:318: sprintf(cmd, "/system/bin/ndc softap fwreload %s AP", SOFTAP_IFACE); ./softap_main.c:354: sprintf(cmd, "/system/bin/ndc softap fwreload %s AP", SOFTAP_IFACE); ./softap_main.c:358: sprintf(cmd, "/system/bin/ndc softap set %s %s open", SOFTAP_IFACE, deviceName); ./softap_main.c:360: sprintf(cmd, "/system/bin/ndc softap set %s %s broadcast 6 wpa2-psk %s", SOFTAP_IFACE, deviceName, passwd); ./softap_main.c:381: system("/system/bin/ndc softap startap"); ./softap_main.c:417: system("/system/bin/ndc ipfwd enable"); ./wifi/lollipop_wifiMonitor.c:429: sprintf(buf, "/system/bin/ndc interface clearaddrs %s", wlan_iface); ./wifi/lollipop_wifiMonitor.c:495: sprintf(buf, "/system/bin/ndc nat enable %s %s 1 192.168.49.1/24", softap_iface, wlan_iface); ./wifi/lollipop_wifiMonitor.c:521: sprintf(buf, "/system/bin/ndc interface clearaddrs %s", wlan_iface); ./wifi/lollipop_wifiMonitor.c:527: sprintf(buf, "/system/bin/ndc nat disable %s %s", softap_iface, wlan_iface);
果然还是lollipop调用了ndc而且是用system去运行的,进去看了下源码,发现每次调用system执行没有检查返回值报错,所以没有调用成功根本不会有报错的log。
最大的坑还是板子上根本没有ndc和netd的执行档,sdk源码中没有包含netd的编译。
又回去检查了一下lollipop里面的Android.mk里面没有写ndc的依赖,源码里面又是调用system执行,所有这种依赖错误也是检查不出来的。
下面开始修正
第一步:
编译net源码,并在init.rc中配置netd的启动服务
service netd /system/bin/netd class main socket netd stream 0660 root system socket dnsproxyd stream 0660 root inet socket mdns stream 0660 root system
第二步:
修改lollipop中的源码在调用system的地方检查返回值报错
重新编译pack,烧录。
本以为这次热点应该起来了,结果大失所望,依然没有热点出来。
ps看了下netd在运行但是hostapd没有。但是log上看到了hostapd打印出了几条有用的错误信息,说明hostapd曾经来过,但是异常退出来了。
E/hostapd ( 1414): Configuration file: /data/misc/wifi/hostapd.conf E/hostapd ( 1414): Could not select hw_mode and channel. (-3) E/hostapd ( 1414): p2p0: Unable to setup interface. E/hostapd ( 1414): Failed to initialize interface
拿着log跟踪了一下hostapd的源码,错误是从第二条开始的,第一条是个提示信息。
现在最早报错的是在操作hw_mod和channel的时候,这两个参数是从hostap.conf获取的,现在检查一下hostapd.conf
interface=p2p0 driver=nl80211 ctrl_interface=/data/misc/wifi/hostapd ssid=LOLLIPOP-ECF21E channel=12345678 ieee80211n=1 hw_mode=g ignore_broadcast_ssid=0
很明显这个conf是有问题的,至少channel是错误的,12345678是设置给hostapd的明文密码, 在增加的log的可以看到调用ndc设置的,而且conf中psk等信息也没配置完全。
接下来就去查找hostapd.conf是在哪里配置的,找到异常的地方
D/lollipop_softap( 1517): 248 run /system/bin/ndc softap fwreload p2p0 AP ok D/lollipop_softap( 1517): 292 run /system/bin/ndc softap set p2p0 LOLLIPOP-ECF21E wpa2-psk 12345678 ok D/lollipop_softap( 1517): 352 run /system/bin/ndc ipfwd enable ok
设置的最初入口是ndc,现在就去找ndc的入口函数
int main(int argc, char **argv) { 39 int sock; 40 int cmdOffset = 0; 41 42 if (argc < 2) 43 usage(argv[0]); 44 45 // try interpreting the first arg as the socket name - if it fails go back to netd 46 47 if ((sock = socket_local_client(argv[1], 48 ANDROID_SOCKET_NAMESPACE_RESERVED, 49 SOCK_STREAM)) < 0) { 50 if ((sock = socket_local_client("netd", 51 ANDROID_SOCKET_NAMESPACE_RESERVED, 52 SOCK_STREAM)) < 0) { 53 fprintf(stderr, "Error connecting (%s)\n", strerror(errno)); 54 exit(4); 55 } 56 } else { 57 if (argc < 3) usage(argv[0]); 58 printf("Using alt socket %s\n", argv[1]); 59 cmdOffset = 1; 60 } 61 62 if (!strcmp(argv[1+cmdOffset], "monitor")) 63 exit(do_monitor(sock, 0)); 64 exit(do_cmd(sock, argc-cmdOffset, &(argv[cmdOffset]))); 65 }
这里是第一个参数当做一个socket本地通信文件,去连接服务器(自己是客户端),如果连接失败了就走默认的netd通信连接,然后把后面的参数以此解析发送给netd服务器。
很明显第一次用socket_local_client连接argv[1]肯定会失败,因为lollipop调用ndc softap的时候自己并没有去启动(可以调用socket_local_server)一个基于softap文件的本地socket服务,所以每次都是连接到了netd,这样才能连接到netd。
现在参数已经传递给netd了,就得去跟踪netd里面是怎么样配置hostapd.conf。
执行/system/bin/ndc softap set p2p0 LOLLIPOP-ECF21E wpa2-psk 12345678的时候netd最后扔给了下面这个
setSoftap
函数
int SoftapController::setSoftap(int argc, char *argv[]) { 115 char psk_str[2*SHA256_DIGEST_LENGTH+1]; 116 int ret = ResponseCode::SoftapStatusResult; 117 int i = 0; 118 int fd; 119 int hidden = 0; 120 int channel = AP_CHANNEL_DEFAULT; 121 char *wbuf = NULL; 122 char *fbuf = NULL; 123 124 if (argc < 5) { 125 ALOGE("Softap set is missing arguments. Please use:"); 126 ALOGE("softap"); 127 return ResponseCode::CommandSyntaxError; 128 } 129 130 if (!strcasecmp(argv[4], "hidden")) 131 hidden = 1; 132 133 if (argc >= 5) { 134 channel = atoi(argv[5]); 135 if (channel <= 0) 136 channel = AP_CHANNEL_DEFAULT; 137 } 138 139 asprintf(&wbuf, "interface=%s\ndriver=nl80211\nctrl_interface=" 140 "/data/misc/wifi/hostapd\nssid=%s\nchannel=%d\nieee80211n=1\n" 141 "hw_mode=g\nignore_broadcast_ssid=%d\n", 142 argv[2], argv[3], channel, hidden); 143 144 if (argc > 7) { 145 if (!strcmp(argv[6], "wpa-psk")) { 146 generatePsk(argv[3], argv[7], psk_str); 147 asprintf(&fbuf, "%swpa=1\nwpa_pairwise=TKIP CCMP\nwpa_psk=%s\n", wbuf, psk_str); 148 } else if (!strcmp(argv[6], "wpa2-psk")) { 149 generatePsk(argv[3], argv[7], psk_str); 150 asprintf(&fbuf, "%swpa=2\nrsn_pairwise=CCMP\nwpa_psk=%s\n", wbuf, psk_str); 151 } else if (!strcmp(argv[6], "open")) { 152 asprintf(&fbuf, "%s", wbuf); 153 }
从126行代码示例使用demo
ALOGE("softap");
以及133-137行代码获取channel来看这个参数解析并不是很智能的,只是根据第几个参数来设置
而lollipop调用
/system/bin/ndc softap set p2p0 LOLLIPOP-ECF21E wpa2-psk 12345678
省略掉了broadcast和channel参数,导致解析出现错位了,把psk当做channel,后面需要解析psk的时候已经没有参数可用了,所以conf文件中没有psk
所以问题的根本原因是lollipop调用ndc的时候参数设置错乱。
找到出错的源码
if (strlen(passwd) == 0) { 286 sprintf(cmd, "/system/bin/ndc softap set %s %s Broadcast %d open", SOFTAP_IFACE, deviceName, channel); 287 } else { 288 sprintf(cmd, "/system/bin/ndc softap set %s %s wpa2-psk %s", 289 SOFTAP_IFACE, deviceName, passwd); 290 } 291 292 if(system(cmd)){ 293 ALOGE("%d run %s failed:%s",__LINE__, cmd, strerror(errno)); 294 } 295 else 296 ALOGD("%d run %s ok",__LINE__, cmd);
改成如下
if (strlen(passwd) == 0) { 286 sprintf(cmd, "/system/bin/ndc softap set %s %s Broadcast %d open", SOFTAP_IFACE, deviceName, channel); 287 } else { 288 sprintf(cmd, "/system/bin/ndc softap set %s %s Broadcast %d wpa2-psk %s", 289 SOFTAP_IFACE, deviceName, channel, passwd); 290 } 291 292 if(system(cmd)){ 293 ALOGE("%d run %s failed:%s",__LINE__, cmd, strerror(errno)); 294 } 295 else 296 ALOGD("%d run %s ok",__LINE__, cmd);
其中channel可以设置一个默认的或者从其他地方获取。
重新编译打包烧录
这次热点已经起来了
查看hostapd.conf
interface=p2p0 driver=nl80211 ctrl_interface=/data/misc/wifi/hostapd ssid=LOLLIPOP-ECF21E channel=6 ieee80211n=1 hw_mode=g ignore_broadcast_ssid=0 wpa=2 rsn_pairwise=CCMP wpa_psk=164934815f6e071f26f8bb59db883daf3ef09bb7df3c6903c082a881370a5d42
这次是ok的了
E/hostapd ( 1538): Configuration file: /data/misc/wifi/hostapd.conf E/hostapd ( 1538): Using interface p2p0 with hwaddr 7e:dd:90:ec:f2:1e and ssid "LOLLIPOP-ECF21E" I/hostapd ( 1538): random: Only 15/20 bytes of strong random data available from /dev/random I/hostapd ( 1538): random: Allow operation to proceed based on internal entropy
这次热点LOLLIPOP-ECF21E已经出来了,可以连接ok。
-----------------------------------------------------------------------------------
现在总结一下调用的流程
首先是lollipop解析lollipop.conf文件获取到DLNA的模式
然后启动了lollipop_softap服务(执行lollipop_softap)
lollipop_softap调用ndc执行档发送了设置和启动指令,设置了hostapd启动参数,并启动softap。
ndc将接收到的指令解析完通过本地socket netd发送参数及命令给netd服务。
netd服务配置生成了hostapd.conf,并调用/system/bin/hostapd启动了hostapd。
lollipop ---> lollipop_softap ---> ndc --> netd --->hostapd