您的位置:首页 > 运维架构 > 网站架构

wpa_supplicant软件架构分析

2012-06-20 20:42 399 查看
http://blog.csdn.net/fxfzz/archive/2011/02/10/6176414.aspx

1. 启动命令

wpa supplicant 在启动时,启动命令可以带有很多参数,目前我们的启动命令如下:

wpa_supplicant /system/bin/wpa_supplicant -Dwext -ieth0 -c/data/wifi/wpa_supplicant.conf -f/data/wifi/wpa_log.txt

wpa_supplicant对于启动命令带的参数,用了两个数据结构来保存,

一个是 wpa_params, 另一个是wpa_interface.

这主要是考虑到wpa_supplicant是可以同时支持多个网络接口的。

wpa_params数据结构主要记录与网络接口无关的一些参数设置。

而每一个网络接口就用一个wpa_interface数据结构来记录。

在启动命令行中,可以用-N来指定将要描述一个新的网络接口,对于一个新的网络接口,可以用下面六个参数描述:

-i<ifname> : 网络接口名称

-c<conf>: 配置文件名称

-C<ctrl_intf>: 控制接口名称

-D<driver>: 驱动类型

-p<driver_param>: 驱动参数

-b<br_ifname>: 桥接口名称

2. wpa_supplicant 初始化流程

2.1. main()函数:

在这个函数中,主要做了四件事。

a. 解析命令行传进的参数。

b. 调用wpa_supplicant_init()函数,做wpa_supplicant的初始化工作。

c. 调用wpa_supplicant_add_iface()函数,增加网络接口。

d. 调用wpa_supplicant_run()函数,让wpa_supplicant真正的run起来。

2.2. wpa_supplicant_init()函数:

a. 打开debug 文件。

b. 注册EAP peer方法。

c. 申请wpa_global内存,该数据结构作为统领其他数据结构的一个核心, 主要包括四个部分:

wpa_supplicant *ifaces /*每个网络接口都有一个对应的wpa_supplicant数据结构,该指针指向最近加入的一个,在wpa_supplicant数据结构中有指针指向next*/

wpa_params params /*启动命令行中带的通用的参数*/

ctrl_iface_global_priv *ctrl_iface /*global 的控制接口*/

ctrl_iface_dbus_priv *dbus_ctrl_iface /*dbus 的控制接口*/

d. 设置wpa_global中的wpa_params中的参数。

e. 调用eloop_init函数将全局变量eloop中的user_data指针指向wpa_global。

f. 调用wpa_supplicant_global_ctrl_iface_init函数初始化global 控制接口。

g. 调用wpa_supplicant_dbus_ctrl_iface_init函数初始化dbus 控制接口。

h. 将该daemon的pid写入pid_file中。

2.3. wpa_supplicant_add_iface()函数:

该函数根据启动命令行中带有的参数增加网络接口, 有几个就增加几个。

a. 因为wpa_supplicant是与网络接口对应的重要的数据结构,所以,首先分配一个wpa_supplicant数据结构的内存。

b. 调用wpa_supplicant_init_iface() 函数来做网络接口的初始工作,主要包括:

设置驱动类型,默认是wext;

读取配置文件,并将其中的信息设置到wpa_supplicant数据结构中的conf 指针指向的数据结构,它是一个wpa_config类型;

命令行设置的控制接口ctrl_interface和驱动参数driver_param覆盖配置文件里设置,命令行中的优先;

拷贝网络接口名称和桥接口名称到wpa_config数据结构;

对于网络配置块有两个链表描述它,一个是 config->ssid,它按照配置文件中的顺序依次挂载在这个链表上,还有一个是pssid,它是一个二级指针,指向一个指针数组,该指针数组按照优先级从高到底的顺序依次保存wpa_ssid指针,相同优先级的在同一链表中挂载。

c. 调用wpa_supplicant_init_iface2() 函数,主要包括:

调用wpa_supplicant_init_eapol()函数来初始化eapol;

调用相应类型的driver的init()函数;

设置driver的param参数;

调用wpa_drv_get_ifname()函数获得网络接口的名称,对于wext类型的driver,没有这个接口函数;

调用wpa_supplicant_init_wpa()函数来初始化wpa,并做相应的初始化工作;

调用wpa_supplicant_driver_init()函数,来初始化driver接口参数;在该函数的最后,会

wpa_s->prev_scan_ssid = BROADCAST_SSID_SCAN;

wpa_supplicant_req_scan(wpa_s, interface_count, 100000);

来主动发起scan,

调用wpa_supplicant_ctrl_iface_init()函数,来初始化控制接口;对于UNIX SOCKET这种方式,其本地socket文件是由配置文件里的ctrl_interface参数指定的路径加上网络接口名称;

2.4. wpa_supplicant_run()函数:

初始化完成之后,让wpa_supplicant的main event loop run起来。

在wpa_supplicant中,有许多与外界通信的socket,它们都是需要注册到eloop event模块中的,具体地说,就是在eloop_sock_table中增加一项记录,其中包括了sock_fd, handle, eloop_data, user_data。

eloop event模块就是将这些socket组织起来,统一管理,然后在eloop_run中利用select机制来管理socket的通信。

3. Wpa_supplicant提供的接口

从通信层次上划分,wpa_supplicant提供向上的控制接口 control interface,用于与其他模块(如UI)进行通信,其他模块可以通过control interface 来获取信息或下发命令。Wpa_supplicant通过socket通信机制实现下行接口,与内核进行通信,获取信息或下发命令。

3.1 上行接口

Wpa_supplicant提供两种方式的上行接口。一种基于传统dbus机制实现与其他进程间的IPC通信;另一种通过Unix domain socket机制实现进程间的IPC通信。

3.1.1 Dbus接口

该接口主要在文件“ctrl_iface_dbus.h”,“ctrl_iface_dbus.c”,“ctrl_iface_dbus_handler.h”和“ctrl_iface_dbus_handler.c”中实现,提供一些基本的控制方法。

DBusMessage * wpas_dbus_new_invalid_iface_error(DBusMessage *message);

DBusMessage * wpas_dbus_global_add_interface(DBusMessage *message,

struct wpa_global *global);

DBusMessage * wpas_dbus_global_remove_interface(DBusMessage *message,

struct wpa_global *global);

DBusMessage * wpas_dbus_global_get_interface(DBusMessage *message,

struct wpa_global *global);

DBusMessage * wpas_dbus_global_set_debugparams(DBusMessage *message,

struct wpa_global *global);

DBusMessage * wpas_dbus_iface_scan(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_scan_results(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_bssid_properties(DBusMessage *message,

struct wpa_supplicant *wpa_s,

struct wpa_scan_res *res);

DBusMessage * wpas_dbus_iface_capabilities(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_add_network(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_remove_network(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_set_network(DBusMessage *message,

struct wpa_supplicant *wpa_s,

struct wpa_ssid *ssid);

DBusMessage * wpas_dbus_iface_enable_network(DBusMessage *message,

struct wpa_supplicant *wpa_s,

struct wpa_ssid *ssid);

DBusMessage * wpas_dbus_iface_disable_network(DBusMessage *message,

struct wpa_supplicant *wpa_s,

struct wpa_ssid *ssid);

DBusMessage * wpas_dbus_iface_select_network(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_disconnect(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_set_ap_scan(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_set_smartcard_modules(

DBusMessage *message, struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_get_state(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_get_scanning(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_set_blobs(DBusMessage *message,

struct wpa_supplicant *wpa_s);

DBusMessage * wpas_dbus_iface_remove_blobs(DBusMessage *message,

struct wpa_supplicant *wpa_s);

3.1.2 Unix domain socket 接口

该接口主要在文件“wpa_ctrl.h”,“wpa_ctrl.c”,“ctrl_iface_unix.c”,“ctrl_iface.h”和“ctrl_iface.c”实现。

(1)“wpa_ctrl.h”,“wpa_ctrl.c”完成对control interface的封装,对外提供统一的接口。其主要的工作是通过Unix domain socket建立一个control interface 的client结点,与作为server的wpa_supplicant结点通信。

主要功能函数:

struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path);

/* 建立并初始化一个Unix domain socket的client结点,并与作为server的wpa_supplicant结点绑定 */

void wpa_ctrl_close(struct wpa_ctrl *ctrl);

/* 撤销并销毁已建立的Unix domain socket的client结点 */

int wpa_ctrl_request(struct wpa_ctrl *ctrl, const char *cmd, size_t cmd_len,

char *reply, size_t *reply_len,

void (*msg_cb)(char *msg, size_t len));

/* 用户模块直接调用该函数对wpa_supplicant发送命令并获取所需信息

* 可以发送的命令如附件1所示 */

Note:

Wpa_supplicant 提供两种由外部模块获取信息的方式:一种是外部模块通过发送request命令然后获取response的问答模式,另一种是wpa_supplicant主动向外部发送event事件,由外部模块监听接收。

一般的常用做法是外部模块通过调用wpa_ctrl_open()两次,建立两个control interface接口,一个为ctrl
interface,用于发送命令,获取信息,另一个为monitor interface,用于监听接收来自于wpa_supplicant的event时间。此举可以降低通信的耦合性,避免response和event的相互干扰。

int wpa_ctrl_attach(struct wpa_ctrl *ctrl);

/* 注册 某个 control interface 作为 monitor interface */

int wpa_ctrl_detach(struct wpa_ctrl *ctrl);

/* 撤销某个 monitor interface 为 普通的 control interface */

int wpa_ctrl_pending(struct wpa_ctrl *ctrl);

/* 判断是否有挂起的event 事件 */

int wpa_ctrl_recv(struct wpa_ctrl *ctrl, char *reply, size_t *reply_len);

/* 获取挂起的event 事件 */

(2)“ctrl_iface_unix.c”实现wpa_supplicant的Unix domain socket通信机制中server结点,完成对client结点的响应。

其中最主要的两个函数为:

static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx,

void *sock_ctx)

/* 接收并解析client发送request命令,然后根据不同的命令调用底层不同的处理函数;

* 然后将获得response结果回馈到 client 结点。

*/

static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv,

int level, const char *buf,

size_t len)

/* 向注册的monitor interfaces 主动发送event事件 */

(3)“ctrl_iface.h”和“ctrl_iface.c”主要实现了各种request命令的底层处理函数。

3.2 下行接口

Wpa_supplicant提供的下行接口主要用于和kernel(driver)进行通信,下发命令和获取信息。

Wpa_supplicant下行接口主要包括三种重要的接口:

1. PF_INET socket接口,主要用于向kernel 发送ioctl命令,控制并获取相应信息。

2. PF_NETLINK socket接口,主要用于接收kernel发送上来的event 事件。

3. PF_PACKET socket接口,主要用于向driver传递802.1X报文。

主要涉及到的文件包括:“driver.h”,“drivers.c”,“driver_wext.h”,“driver_wext.c”,“l2_packet.h”和“l2_packet_linux.c”。其中“driver.h”,“drivers.c”,“driver_wext.h”和“driver_wext.c”实现PF_INETsocket接口和PF_NETLINK
socket接口;“l2_packet.h”和“l2_packet_linux.c”实现PF_PACKET socket接口。

(1)“driver.h”,“drivers.c”主要用于封装底层差异对外显示一个相同的wpa_driver_ops接口。Wpa_supplicant可支持atmel, Broadcom, ipw, madwifi, ndis, nl80211, wext等多种驱动。

其中一个最主要的数据结构为wpa_driver_ops, 其定义了driver相关的各种操作接口。

(2)“driver_wext.h”,“driver_wext.c”实现了wext形式的wpa_driver_ops,并创建了PF_INETsocket接口和PF_NETLINK
socket接口,然后通过这两个接口完成与kernel的信息交互。

Wext提供的一个主要数据结构为:

struct wpa_driver_wext_data {

void *ctx;

int event_sock;

int ioctl_sock;

int mlme_sock;

char ifname[IFNAMSIZ + 1];

int ifindex;

int ifindex2;

int if_removed;

u8 *assoc_req_ies;

size_t assoc_req_ies_len;

u8 *assoc_resp_ies;

size_t assoc_resp_ies_len;

struct wpa_driver_capa capa;

int has_capability;

int we_version_compiled;

/* for set_auth_alg fallback */

int use_crypt;

int auth_alg_fallback;

int operstate;

char mlmedev[IFNAMSIZ + 1];

int scan_complete_events;

};

其中event_sock 为PF_NETLINK socket接口,ioctl_sock为PF_INET
socket借口。

Driver_wext.c实现了大量底层处理函数用于实现wpa_driver_ops操作参数,其中比较重要的有:

void * wpa_driver_wext_init(void *ctx, const char *ifname);

/* 初始化wpa_driver_wext_data 数据结构,并创建PF_NETLINK socket和
PF_INET
socket 接口 */

void wpa_driver_wext_deinit(void *priv);

/* 销毁wpa_driver_wext_data 数据结构,PF_NETLINK socket和 PF_INETsocket 接口 */

static void wpa_driver_wext_event_receive(int sock, void *eloop_ctx,

void *sock_ctx);

/* 处理kernel主动发送的event事件的 callback 函数 */

最后,将实现的操作函数映射到一个全局的wpa_driver_ops类型数据结构 wpa_driver_wext_ops中。

const struct wpa_driver_ops wpa_driver_wext_ops = {

.name = "wext",

.desc = "Linux wireless extensions (generic)",

.get_bssid = wpa_driver_wext_get_bssid,

.get_ssid = wpa_driver_wext_get_ssid,

.set_wpa = wpa_driver_wext_set_wpa,

.set_key = wpa_driver_wext_set_key,

.set_countermeasures = wpa_driver_wext_set_countermeasures,

.set_drop_unencrypted = wpa_driver_wext_set_drop_unencrypted,

.scan = wpa_driver_wext_scan,

.get_scan_results2 = wpa_driver_wext_get_scan_results,

.deauthenticate = wpa_driver_wext_deauthenticate,

.disassociate = wpa_driver_wext_disassociate,

.set_mode = wpa_driver_wext_set_mode,

.associate = wpa_driver_wext_associate,

.set_auth_alg = wpa_driver_wext_set_auth_alg,

.init = wpa_driver_wext_init,

.deinit = wpa_driver_wext_deinit,

.add_pmkid = wpa_driver_wext_add_pmkid,

.remove_pmkid = wpa_driver_wext_remove_pmkid,

.flush_pmkid = wpa_driver_wext_flush_pmkid,

.get_capa = wpa_driver_wext_get_capa,

.set_operstate = wpa_driver_wext_set_operstate,

};

(3)“l2_packet.h”和“l2_packet_linux.c”主要用于实现PF_PACKET socket接口,通过该接口,wpa_supplicant可以直接将802.1X packet发送到L2层,而不经过TCP/IP协议栈。

其中主要的功能函数为:

struct l2_packet_data * l2_packet_init(

const char *ifname, const u8 *own_addr, unsigned short protocol,

void (*rx_callback)(void *ctx, const u8 *src_addr,

const u8 *buf, size_t len),

void *rx_callback_ctx, int l2_hdr);

/* 创建并初始化PF_PACKET socket接口,其中rx_callback 为从L2接收到的packet 处理callback函数 */

void l2_packet_deinit(struct l2_packet_data *l2);

/* 销毁 PF_PACKET socket接口 */

int l2_packet_send(struct l2_packet_data *l2, const u8 *dst_addr, u16 proto,

const u8 *buf, size_t len);

/* L2层packet发送函数,wpa_supplicant用此发送L2层 802.1X packet */

static void l2_packet_receive(int sock, void *eloop_ctx, void *sock_ctx);

/* L2层packet接收函数,接收来自L2层数据后,将其发送到上层 */

4. Control interface commands

PING

MIB

STATUS

STATUS-VERBOSE

PMKSA

SET <variable> <valus>

LOGON

LOGOFF

REASSOCIATE

RECONNECT

PREAUTH <BSSID>

ATTACH

DETACH

LEVEL <debug level>

RECONFIGURE

TERMINATE

BSSID <network id> <BSSID>

LIST_NETWORKS

DISCONNECT

SCAN

SCAN_RESULTS

BSS

SELECT_NETWORK <network id>

ENABLE_NETWORK <network id>

DISABLE_NETWORK <network id>

ADD_NETWORK

REMOVE_NETWORK <network id>

SET_NETWORK <network id> <variable> <value>

GET_NETWORK <network id> <variable>

SAVE_CONFIG

wpa_supplicant 初始化流程分析

1. 启动命令

wpa supplicant 在启动时,启动命令可以带有很多参数,目前我们的启动命令如下:

wpa_supplicant /system/bin/wpa_supplicant -Dwext -ieth0 -c/data/wifi/wpa_supplicant.conf -f/data/wifi/wpa_log.txt

wpa_supplicant对于启动命令带的参数,用了两个数据结构来保存,

一个是 wpa_params, 另一个是wpa_interface.

这主要是考虑到wpa_supplicant是可以同时支持多个网络接口的。

wpa_params数据结构主要记录与网络接口无关的一些参数设置。

而每一个网络接口就用一个wpa_interface数据结构来记录。

在启动命令行中,可以用-N来指定将要描述一个新的网络接口,对于一个新的网络接口,可以用下面六个参数描述:

-i<ifname> : 网络接口名称

-c<conf>: 配置文件名称

-C<ctrl_intf>: 控制接口名称

-D<driver>: 驱动类型

-p<driver_param>: 驱动参数

-b<br_ifname>: 桥接口名称

2. wpa_supplicant 初始化流程

2.1. main()函数:


在这个函数中,主要做了四件事。

a.

解析命令行传进的参数。

b.

调用wpa_supplicant_init()函数,做wpa_supplicant的初始化工作。

c.

调用wpa_supplicant_add_iface()函数,增加网络接口。

d.

调用wpa_supplicant_run()函数,让wpa_supplicant真正的run起来。

2.2. wpa_supplicant_init()函数:

a.


打开debug 文件。

b.

注册EAP peer方法。

c.

申请wpa_global内存,该数据结构作为统领其他数据结构的一个核心, 主要包括四个部分:

wpa_supplicant *ifaces

/*每个网络接口都有一个对应的wpa_supplicant数据结构,该指针指向最近加入的一个,在wpa_supplicant数据结构中有指针指向next*/

wpa_params params

/*启动命令行中带的通用的参数*/

ctrl_iface_global_priv *ctrl_iface

/*global 的控制接口*/

ctrl_iface_dbus_priv *dbus_ctrl_iface

/*dbus 的控制接口*/

d.

设置wpa_global中的wpa_params中的参数。

e.

调用eloop_init函数将全局变量eloop中的user_data指针指向wpa_global。

f.

调用wpa_supplicant_global_ctrl_iface_init函数初始化global 控制接口。

g.

调用wpa_supplicant_dbus_ctrl_iface_init函数初始化dbus 控制接口。

h.

将该daemon的pid写入pid_file中。

2.3. wpa_supplicant_add_iface()函数:

该函数根据启动命令行中带有的参数增加网络接口, 有几个就增加几个。

a.

因为wpa_supplicant是与网络接口对应的重要的数据结构,所以,首先分配一个wpa_supplicant数据结构的内存。

b.

调用wpa_supplicant_init_iface() 函数来做网络接口的初始工作,主要包括:

设置驱动类型,默认是wext;

读取配置文件,并将其中的信息设置到wpa_supplicant数据结构中的conf 指针指向的数据结构,它是一个wpa_config类型;

命令行设置的控制接口ctrl_interface和驱动参数driver_param覆盖配置文件里设置,命令行中的优先;

拷贝网络接口名称和桥接口名称到wpa_config数据结构;

对 于网络配置块有两个链表描述它,一个是 config->ssid,它按照配置文件中的顺序依次挂载在这个链表上,还有一个是pssid,它是一个二级指针,指向一个指针数组,该指针数组 按照优先级从高到底的顺序依次保存wpa_ssid指针,相同优先级的在同一链表中挂载。

c.

调用wpa_supplicant_init_iface2() 函数,主要包括:

调用wpa_supplicant_init_eapol()函数来初始化eapol;

调用相应类型的driver的init()函数;

设置driver的param参数;

调用wpa_drv_get_ifname()函数获得网络接口的名称,对于wext类型的driver,没有这个接口函数;

调用wpa_supplicant_init_wpa()函数来初始化wpa,并做相应的初始化工作;

调用wpa_supplicant_driver_init()函数,来初始化driver接口参数;在该函数的最后,会

wpa_s->prev_scan_ssid = BROADCAST_SSID_SCAN;

wpa_supplicant_req_scan(wpa_s, interface_count, 100000);

来主动发起scan,

调用wpa_supplicant_ctrl_iface_init()函数,来初始化控制接口;对于UNIX SOCKET这种方式,其本地socket文件是由配置文件里的ctrl_interface参数指定的路径加上网络接口名称;

2.4. wpa_supplicant_run()函数:

初始化完成之后,让wpa_supplicant的main event loop run起来。

在 wpa_supplicant中,有许多与外界通信的socket,它们都是需要注册到eloop event模块中的,具体地说,就是在eloop_sock_table中增加一项记录,其中包括了sock_fd, handle, eloop_data, user_data。

eloop event模块就是将这些socket组织起来,统一管理,然后在eloop_run中利用select机制来管理socket的通信。

3. wpa_supplicant 的对外接口分析

对于wpa_supplicant模块的对外接口,主要有以下几种:

3.1

. global control interface: 用于配置(增加或删除)网络接口。

3.2

. ctrl interface: 与其他外部模块交互的控制接口。

例 如,在初始化时,android 平台的wifi.c中的 wifi_connect_to_supplicant函数调用wpa_ctrl_open函数创建两个socket,一个是ctrl interface,另一个就是monitor interface,monitor interface这个接口用于监测从wpa_supplicant发出的event事件。

这两个socket创建成功后,monitor interface 会发送ATTACH到wpa_supplicant模块,wpa_supplicant模块收到后,会将该客户端的socket信息记录下来,用于以后发送事件时用(由于用的是DGRAM的方式)。

3.3

. socket for ioctl: 发送命令到kernel space。

3.4

. socket (netlink) for interact between kernel and userspace(AF_NETLINK, NETLINK_ROUTE): 接受kernel发送上来的event。

3.5

. socket for l2 packet(PF_PACKET): 处理802.1x报文。
1. 启动命令

wpa supplicant 在启动时,启动命令可以带有很多参数,目前我们的启动命令如下:

wpa_supplicant /system/bin/wpa_supplicant -Dwext -ieth0 -c/data/wifi/wpa_supplicant.conf -f/data/wifi/wpa_log.txt

wpa_supplicant对于启动命令带的参数,用了两个数据结构来保存,

一个是 wpa_params, 另一个是wpa_interface.

这主要是考虑到wpa_supplicant是可以同时支持多个网络接口的。

wpa_params数据结构主要记录与网络接口无关的一些参数设置。

而每一个网络接口就用一个wpa_interface数据结构来记录。

在启动命令行中,可以用-N来指定将要描述一个新的网络接口,对于一个新的网络接口,可以用下面六个参数描述:

-i<ifname> : 网络接口名称

-c<conf>: 配置文件名称

-C<ctrl_intf>: 控制接口名称

-D<driver>: 驱动类型

-p<driver_param>: 驱动参数

-b<br_ifname>: 桥接口名称

2. wpa_supplicant 初始化流程

2.1. main()函数:


在这个函数中,主要做了四件事。

a.

解析命令行传进的参数。

b.

调用wpa_supplicant_init()函数,做wpa_supplicant的初始化工作。

c.

调用wpa_supplicant_add_iface()函数,增加网络接口。

d.

调用wpa_supplicant_run()函数,让wpa_supplicant真正的run起来。

2.2. wpa_supplicant_init()函数:

a.


打开debug 文件。

b.

注册EAP peer方法。

c.

申请wpa_global内存,该数据结构作为统领其他数据结构的一个核心, 主要包括四个部分:

wpa_supplicant *ifaces

/*每个网络接口都有一个对应的wpa_supplicant数据结构,该指针指向最近加入的一个,在wpa_supplicant数据结构中有指针指向next*/

wpa_params params

/*启动命令行中带的通用的参数*/

ctrl_iface_global_priv *ctrl_iface

/*global 的控制接口*/

ctrl_iface_dbus_priv *dbus_ctrl_iface

/*dbus 的控制接口*/

d.

设置wpa_global中的wpa_params中的参数。

e.

调用eloop_init函数将全局变量eloop中的user_data指针指向wpa_global。

f.

调用wpa_supplicant_global_ctrl_iface_init函数初始化global 控制接口。

g.

调用wpa_supplicant_dbus_ctrl_iface_init函数初始化dbus 控制接口。

h.

将该daemon的pid写入pid_file中。

2.3. wpa_supplicant_add_iface()函数:

该函数根据启动命令行中带有的参数增加网络接口, 有几个就增加几个。

a.

因为wpa_supplicant是与网络接口对应的重要的数据结构,所以,首先分配一个wpa_supplicant数据结构的内存。

b.

调用wpa_supplicant_init_iface() 函数来做网络接口的初始工作,主要包括:

设置驱动类型,默认是wext;

读取配置文件,并将其中的信息设置到wpa_supplicant数据结构中的conf 指针指向的数据结构,它是一个wpa_config类型;

命令行设置的控制接口ctrl_interface和驱动参数driver_param覆盖配置文件里设置,命令行中的优先;

拷贝网络接口名称和桥接口名称到wpa_config数据结构;

对 于网络配置块有两个链表描述它,一个是 config->ssid,它按照配置文件中的顺序依次挂载在这个链表上,还有一个是pssid,它是一个二级指针,指向一个指针数组,该指针数组 按照优先级从高到底的顺序依次保存wpa_ssid指针,相同优先级的在同一链表中挂载。

c.

调用wpa_supplicant_init_iface2() 函数,主要包括:

调用wpa_supplicant_init_eapol()函数来初始化eapol;

调用相应类型的driver的init()函数;

设置driver的param参数;

调用wpa_drv_get_ifname()函数获得网络接口的名称,对于wext类型的driver,没有这个接口函数;

调用wpa_supplicant_init_wpa()函数来初始化wpa,并做相应的初始化工作;

调用wpa_supplicant_driver_init()函数,来初始化driver接口参数;在该函数的最后,会

wpa_s->prev_scan_ssid = BROADCAST_SSID_SCAN;

wpa_supplicant_req_scan(wpa_s, interface_count, 100000);

来主动发起scan,

调用wpa_supplicant_ctrl_iface_init()函数,来初始化控制接口;对于UNIX SOCKET这种方式,其本地socket文件是由配置文件里的ctrl_interface参数指定的路径加上网络接口名称;

2.4. wpa_supplicant_run()函数:

初始化完成之后,让wpa_supplicant的main event loop run起来。

在 wpa_supplicant中,有许多与外界通信的socket,它们都是需要注册到eloop event模块中的,具体地说,就是在eloop_sock_table中增加一项记录,其中包括了sock_fd, handle, eloop_data, user_data。

eloop event模块就是将这些socket组织起来,统一管理,然后在eloop_run中利用select机制来管理socket的通信。

3. wpa_supplicant 的对外接口分析

对于wpa_supplicant模块的对外接口,主要有以下几种:

3.1

. global control interface: 用于配置(增加或删除)网络接口。

3.2

. ctrl interface: 与其他外部模块交互的控制接口。

例 如,在初始化时,android 平台的wifi.c中的 wifi_connect_to_supplicant函数调用wpa_ctrl_open函数创建两个socket,一个是ctrl interface,另一个就是monitor interface,monitor interface这个接口用于监测从wpa_supplicant发出的event事件。

这两个socket创建成功后,monitor interface 会发送ATTACH到wpa_supplicant模块,wpa_supplicant模块收到后,会将该客户端的socket信息记录下来,用于以后发送事件时用(由于用的是DGRAM的方式)。

3.3

. socket for ioctl: 发送命令到kernel space。

3.4

. socket (netlink) for interact between kernel and userspace(AF_NETLINK, NETLINK_ROUTE): 接受kernel发送上来的event。

3.5

. socket for l2 packet(PF_PACKET): 处理802.1x报文。
1. 启动命令

wpa supplicant 在启动时,启动命令可以带有很多参数,目前我们的启动命令如下:

wpa_supplicant /system/bin/wpa_supplicant -Dwext -ieth0 -c/data/wifi/wpa_supplicant.conf -f/data/wifi/wpa_log.txt

wpa_supplicant对于启动命令带的参数,用了两个数据结构来保存,

一个是 wpa_params, 另一个是wpa_interface.

这主要是考虑到wpa_supplicant是可以同时支持多个网络接口的。

wpa_params数据结构主要记录与网络接口无关的一些参数设置。

而每一个网络接口就用一个wpa_interface数据结构来记录。

在启动命令行中,可以用-N来指定将要描述一个新的网络接口,对于一个新的网络接口,可以用下面六个参数描述:

-i<ifname> : 网络接口名称

-c<conf>: 配置文件名称

-C<ctrl_intf>: 控制接口名称

-D<driver>: 驱动类型

-p<driver_param>: 驱动参数

-b<br_ifname>: 桥接口名称

2. wpa_supplicant 初始化流程

2.1. main()函数:


在这个函数中,主要做了四件事。

a.

解析命令行传进的参数。

b.

调用wpa_supplicant_init()函数,做wpa_supplicant的初始化工作。

c.

调用wpa_supplicant_add_iface()函数,增加网络接口。

d.

调用wpa_supplicant_run()函数,让wpa_supplicant真正的run起来。

2.2. wpa_supplicant_init()函数:

a.


打开debug 文件。

b.

注册EAP peer方法。

c.

申请wpa_global内存,该数据结构作为统领其他数据结构的一个核心, 主要包括四个部分:

wpa_supplicant *ifaces

/*每个网络接口都有一个对应的wpa_supplicant数据结构,该指针指向最近加入的一个,在wpa_supplicant数据结构中有指针指向next*/

wpa_params params

/*启动命令行中带的通用的参数*/

ctrl_iface_global_priv *ctrl_iface

/*global 的控制接口*/

ctrl_iface_dbus_priv *dbus_ctrl_iface

/*dbus 的控制接口*/

d.

设置wpa_global中的wpa_params中的参数。

e.

调用eloop_init函数将全局变量eloop中的user_data指针指向wpa_global。

f.

调用wpa_supplicant_global_ctrl_iface_init函数初始化global 控制接口。

g.

调用wpa_supplicant_dbus_ctrl_iface_init函数初始化dbus 控制接口。

h.

将该daemon的pid写入pid_file中。

2.3. wpa_supplicant_add_iface()函数:

该函数根据启动命令行中带有的参数增加网络接口, 有几个就增加几个。

a.

因为wpa_supplicant是与网络接口对应的重要的数据结构,所以,首先分配一个wpa_supplicant数据结构的内存。

b.

调用wpa_supplicant_init_iface() 函数来做网络接口的初始工作,主要包括:

设置驱动类型,默认是wext;

读取配置文件,并将其中的信息设置到wpa_supplicant数据结构中的conf 指针指向的数据结构,它是一个wpa_config类型;

命令行设置的控制接口ctrl_interface和驱动参数driver_param覆盖配置文件里设置,命令行中的优先;

拷贝网络接口名称和桥接口名称到wpa_config数据结构;

对 于网络配置块有两个链表描述它,一个是 config->ssid,它按照配置文件中的顺序依次挂载在这个链表上,还有一个是pssid,它是一个二级指针,指向一个指针数组,该指针数组 按照优先级从高到底的顺序依次保存wpa_ssid指针,相同优先级的在同一链表中挂载。

c.

调用wpa_supplicant_init_iface2() 函数,主要包括:

调用wpa_supplicant_init_eapol()函数来初始化eapol;

调用相应类型的driver的init()函数;

设置driver的param参数;

调用wpa_drv_get_ifname()函数获得网络接口的名称,对于wext类型的driver,没有这个接口函数;

调用wpa_supplicant_init_wpa()函数来初始化wpa,并做相应的初始化工作;

调用wpa_supplicant_driver_init()函数,来初始化driver接口参数;在该函数的最后,会

wpa_s->prev_scan_ssid = BROADCAST_SSID_SCAN;

wpa_supplicant_req_scan(wpa_s, interface_count, 100000);

来主动发起scan,

调用wpa_supplicant_ctrl_iface_init()函数,来初始化控制接口;对于UNIX SOCKET这种方式,其本地socket文件是由配置文件里的ctrl_interface参数指定的路径加上网络接口名称;

2.4. wpa_supplicant_run()函数:

初始化完成之后,让wpa_supplicant的main event loop run起来。

在 wpa_supplicant中,有许多与外界通信的socket,它们都是需要注册到eloop event模块中的,具体地说,就是在eloop_sock_table中增加一项记录,其中包括了sock_fd, handle, eloop_data, user_data。

eloop event模块就是将这些socket组织起来,统一管理,然后在eloop_run中利用select机制来管理socket的通信。

3. wpa_supplicant 的对外接口分析

对于wpa_supplicant模块的对外接口,主要有以下几种:

3.1

. global control interface: 用于配置(增加或删除)网络接口。

3.2

. ctrl interface: 与其他外部模块交互的控制接口。

例 如,在初始化时,android 平台的wifi.c中的 wifi_connect_to_supplicant函数调用wpa_ctrl_open函数创建两个socket,一个是ctrl interface,另一个就是monitor interface,monitor interface这个接口用于监测从wpa_supplicant发出的event事件。

这两个socket创建成功后,monitor interface 会发送ATTACH到wpa_supplicant模块,wpa_supplicant模块收到后,会将该客户端的socket信息记录下来,用于以后发送事件时用(由于用的是DGRAM的方式)。

3.3

. socket for ioctl: 发送命令到kernel space。

3.4

. socket (netlink) for interact between kernel and userspace(AF_NETLINK, NETLINK_ROUTE): 接受kernel发送上来的event。

3.5

. socket for l2 packet(PF_PACKET): 处理802.1x报文。
1. 启动命令

wpa supplicant 在启动时,启动命令可以带有很多参数,目前我们的启动命令如下:

wpa_supplicant /system/bin/wpa_supplicant -Dwext -ieth0 -c/data/wifi/wpa_supplicant.conf -f/data/wifi/wpa_log.txt

wpa_supplicant对于启动命令带的参数,用了两个数据结构来保存,

一个是 wpa_params, 另一个是wpa_interface.

这主要是考虑到wpa_supplicant是可以同时支持多个网络接口的。

wpa_params数据结构主要记录与网络接口无关的一些参数设置。

而每一个网络接口就用一个wpa_interface数据结构来记录。

在启动命令行中,可以用-N来指定将要描述一个新的网络接口,对于一个新的网络接口,可以用下面六个参数描述:

-i<ifname> : 网络接口名称

-c<conf>: 配置文件名称

-C<ctrl_intf>: 控制接口名称

-D<driver>: 驱动类型

-p<driver_param>: 驱动参数

-b<br_ifname>: 桥接口名称

2. wpa_supplicant 初始化流程

2.1. main()函数:


在这个函数中,主要做了四件事。

a.

解析命令行传进的参数。

b.

调用wpa_supplicant_init()函数,做wpa_supplicant的初始化工作。

c.

调用wpa_supplicant_add_iface()函数,增加网络接口。

d.

调用wpa_supplicant_run()函数,让wpa_supplicant真正的run起来。

2.2. wpa_supplicant_init()函数:

a.


打开debug 文件。

b.

注册EAP peer方法。

c.

申请wpa_global内存,该数据结构作为统领其他数据结构的一个核心, 主要包括四个部分:

wpa_supplicant *ifaces

/*每个网络接口都有一个对应的wpa_supplicant数据结构,该指针指向最近加入的一个,在wpa_supplicant数据结构中有指针指向next*/

wpa_params params

/*启动命令行中带的通用的参数*/

ctrl_iface_global_priv *ctrl_iface

/*global 的控制接口*/

ctrl_iface_dbus_priv *dbus_ctrl_iface

/*dbus 的控制接口*/

d.

设置wpa_global中的wpa_params中的参数。

e.

调用eloop_init函数将全局变量eloop中的user_data指针指向wpa_global。

f.

调用wpa_supplicant_global_ctrl_iface_init函数初始化global 控制接口。

g.

调用wpa_supplicant_dbus_ctrl_iface_init函数初始化dbus 控制接口。

h.

将该daemon的pid写入pid_file中。

2.3. wpa_supplicant_add_iface()函数:

该函数根据启动命令行中带有的参数增加网络接口, 有几个就增加几个。

a.

因为wpa_supplicant是与网络接口对应的重要的数据结构,所以,首先分配一个wpa_supplicant数据结构的内存。

b.

调用wpa_supplicant_init_iface() 函数来做网络接口的初始工作,主要包括:

设置驱动类型,默认是wext;

读取配置文件,并将其中的信息设置到wpa_supplicant数据结构中的conf 指针指向的数据结构,它是一个wpa_config类型;

命令行设置的控制接口ctrl_interface和驱动参数driver_param覆盖配置文件里设置,命令行中的优先;

拷贝网络接口名称和桥接口名称到wpa_config数据结构;

对 于网络配置块有两个链表描述它,一个是 config->ssid,它按照配置文件中的顺序依次挂载在这个链表上,还有一个是pssid,它是一个二级指针,指向一个指针数组,该指针数组 按照优先级从高到底的顺序依次保存wpa_ssid指针,相同优先级的在同一链表中挂载。

c.

调用wpa_supplicant_init_iface2() 函数,主要包括:

调用wpa_supplicant_init_eapol()函数来初始化eapol;

调用相应类型的driver的init()函数;

设置driver的param参数;

调用wpa_drv_get_ifname()函数获得网络接口的名称,对于wext类型的driver,没有这个接口函数;

调用wpa_supplicant_init_wpa()函数来初始化wpa,并做相应的初始化工作;

调用wpa_supplicant_driver_init()函数,来初始化driver接口参数;在该函数的最后,会

wpa_s->prev_scan_ssid = BROADCAST_SSID_SCAN;

wpa_supplicant_req_scan(wpa_s, interface_count, 100000);

来主动发起scan,

调用wpa_supplicant_ctrl_iface_init()函数,来初始化控制接口;对于UNIX SOCKET这种方式,其本地socket文件是由配置文件里的ctrl_interface参数指定的路径加上网络接口名称;

2.4. wpa_supplicant_run()函数:

初始化完成之后,让wpa_supplicant的main event loop run起来。

在 wpa_supplicant中,有许多与外界通信的socket,它们都是需要注册到eloop event模块中的,具体地说,就是在eloop_sock_table中增加一项记录,其中包括了sock_fd, handle, eloop_data, user_data。

eloop event模块就是将这些socket组织起来,统一管理,然后在eloop_run中利用select机制来管理socket的通信。

3. wpa_supplicant 的对外接口分析

对于wpa_supplicant模块的对外接口,主要有以下几种:

3.1

. global control interface: 用于配置(增加或删除)网络接口。

3.2

. ctrl interface: 与其他外部模块交互的控制接口。

例 如,在初始化时,android 平台的wifi.c中的 wifi_connect_to_supplicant函数调用wpa_ctrl_open函数创建两个socket,一个是ctrl interface,另一个就是monitor interface,monitor interface这个接口用于监测从wpa_supplicant发出的event事件。

这两个socket创建成功后,monitor interface 会发送ATTACH到wpa_supplicant模块,wpa_supplicant模块收到后,会将该客户端的socket信息记录下来,用于以后发送事件时用(由于用的是DGRAM的方式)。

3.3

. socket for ioctl: 发送命令到kernel space。

3.4

. socket (netlink) for interact between kernel and userspace(AF_NETLINK, NETLINK_ROUTE): 接受kernel发送上来的event。

3.5

. socket for l2 packet(PF_PACKET): 处理802.1x报文。

用wpa_cli 连接无线网络

用wpa_cli 连接无线网络

wpa_supplicant软件包中包含客户端程序 wpa_cli,通过它可以直接连接无线网络,不需要通常的无线网络配置文件的方式,这在某些特殊情况下是有用的。

下面通过一个例子来说明wpa_cli 的使用:

1、启动wpa_supplicant

以daemon方式启动wpa_supplicat:

wpa_supplicant -B -i wlan0 -D wext -c /etc/wpa_supplicant.conf

其中的wlan0为系统内的无线网卡的设备名。配置文件是/etc/wpa_supplicant.conf,其中不包含要连接的无线网络的配置信息。最简单的配置文件只包含一句配置语句即可:

ctrl_interface=/var/run/wpa_supplicant

2、启动wpa_cli

wpa_cli -i wlan0

如果系统内只有一个无线网卡,则-i wlan0参数可省略。程序启动后,进入交户操作状态,可接受用户的命令输入。下面的命令都是在这个交互操作模式下输入的。

3、增加网络,并设置网络参数

可以先用status命令查讯网络连接状态,应该返回

wpa_state=DISCONNECTED

用下面的命令增加网络,并设置相应的参数

add_network

该命令会返回新增加的网络的ID,一般是0 。下面的命令的第一个参数就是网络的ID。

set_network 0 ssid "wyk"

wyk是无线网络名称,要用引号围起来。

set_network 0 psk "password"

password代表网络的密码,也要用引号围起来。

有这两个网络参数一般就够了。

4、启用网络

enable_network 0

命令执行后,wpa_cli 会输出连接的过程信息。如果一切正确,则最后后输出:

<2>CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed (reauth) [id=0 id_str=]

5、获取IP地址



quit

命令退出wpa_cli 。然后用

dhclient wlan0

命令获取IP地址。

Android WIFI 分析

转自:http://stulog.com/?post=194





初始化

在 SystemServer 启动的时候,会生成一个 ConnectivityService 的实例,

try {

Log.i(TAG, “Starting Connectivity Service.”);

ServiceManager.addService(Context.CONNECTIVITY_SERVICE, new ConnectivityService(context));

} catch (Throwable e) {

Log.e(TAG, “Failure starting Connectivity Service”, e);

}

ConnectivityService 的构造函数会创建 WifiService,

if (DBG) Log.v(TAG, “Starting Wifi Service.”);

mWifiStateTracker = new WifiStateTracker(context, handler);

WifiService wifiService = new WifiService(context, mWifiStateTracker);

ServiceManager.addService(Context.WIFI_SERVICE, wifiService);

WifiStateTracker 会创建 WifiMonitor 接收来自底层的事件,WifiService 和 WifiMonitor 是整个模块的核心。WifiService 负责启动关闭 wpa_supplicant、启动关闭 WifiMonitor 监视线程和把命令下发给 wpa_supplicant,而 WifiMonitor 则负责从 wpa_supplicant 接收事件通知。

连接 AP

1. 使能 WIFI

WirelessSettings 在初始化的时候配置了由 WifiEnabler 来处理 Wifi 按钮,

private void initToggles() {

mWifiEnabler = new WifiEnabler(

this,

(WifiManager) getSystemService(WIFI_SERVICE),

(CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI));

当用户按下 Wifi 按钮后, Android 会调用 WifiEnabler 的 onPreferenceChange, 再由 WifiEnabler调用 WifiManager 的 setWifiEnabled 接口函数,通过 AIDL,实际调用的是 WifiService 的setWifiEnabled 函数, WifiService 接着向自身发送一条 MESSAGE_ENABLE_WIFI 消息,在处理该消息的代码中做真正的使能工作:首先装载 WIFI 内核模块(该模块的位置硬编码为”/system/lib/modules/wlan.ko”
), 然后启动 wpa_supplicant ( 配置文件硬编码为”/data/misc/wifi/wpa_supplicant.conf”) 再通过 WifiStateTracker 来启动 WifiMonitor 中的监视线程。

private boolean setWifiEnabledBlocking(boolean enable) {

final int eventualWifiState = enable ? WIFI_STATE_ENABLED : WIFI_STATE_DISABLED;

updateWifiState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING);

if (enable) {

if (!WifiNative.loadDriver()) {

Log.e(TAG, “Failed to load Wi-Fi driver.”);

updateWifiState(WIFI_STATE_UNKNOWN);

return false;

}

if (!WifiNative.startSupplicant()) {

WifiNative.unloadDriver();

Log.e(TAG, “Failed to start supplicant daemon.”);

updateWifiState(WIFI_STATE_UNKNOWN);

return false;

}

mWifiStateTracker.startEventLoop();

}

// Success!

persistWifiEnabled(enable);

updateWifiState(eventualWifiState);

return true;

}

当使能成功后,会广播发送 WIFI_STATE_CHANGED_ACTION 这个 Intent 通知外界 WIFI已经成功使能了。WifiEnabler 创建的时候就会向Android 注册接收WIFI_STATE_CHANGED_ACTION,因此它会收到该 Intent,从而开始扫描。

private void handleWifiStateChanged(int wifiState) {

if (wifiState == WIFI_STATE_ENABLED) {

loadConfiguredAccessPoints();

attemptScan();

}

}

2. 查找 AP

扫描的入口函数是 WifiService 的 startScan,它其实也就是往 wpa_supplicant 发送 SCAN 命令。

static jboolean android_net_wifi_scanCommand(JNIEnv* env, jobject clazz)

{

jboolean result;

// Ignore any error from setting the scan mode.

// The scan will still work.

(void)doBooleanCommand(“DRIVER SCAN-ACTIVE”, “OK”);

result = doBooleanCommand(“SCAN”, “OK”);

(void)doBooleanCommand(“DRIVER SCAN-PASSIVE”, “OK”);

return result;

}

当 wpa_supplicant 处理完 SCAN 命令后,它会向控制通道发送事件通知扫描完成,从而wifi_wait_for_event 函数会接收到该事件,由此 WifiMonitor 中的 MonitorThread 会被执行来出来这个事件,

void handleEvent(int event, String remainder) {

case SCAN_RESULTS:

mWifiStateTracker.notifyScanResultsAvailable();

break;

WifiStateTracker 则接着广播发送 SCAN_RESULTS_AVAILABLE_ACTION 这个 Intent

case EVENT_SCAN_RESULTS_AVAILABLE:

mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));

WifiLayer 注册了接收 SCAN_RESULTS_AVAILABLE_ACTION 这个 Intent,所以它的相关处理函数 handleScanResultsAvailable 会被调用,在该函数中,先会去拿到 SCAN 的结果(最终是往 wpa_supplicant 发送 SCAN_RESULT 命令并读取返回值来实现的),

List<ScanResult> list = mWifiManager.getScanResults();

对每一个扫描返回的 AP,WifiLayer 会调用 WifiSettings 的 onAccessPointSetChanged 函数,

从而最终把该 AP 加到 GUI 显示列表中。

public void onAccessPointSetChanged(AccessPointState ap, boolean added) {

AccessPointPreference pref = mAps.get(ap);

if (added) {

if (pref == null) {

pref = new AccessPointPreference(this, ap);

mAps.put(ap, pref);

} else {

pref.setEnabled(true);

}

mApCategory.addPreference(pref);

}

}

3. 配置 AP 参数

当用户在 WifiSettings 界面上选择了一个 AP 后,会显示配置 AP 参数的一个对话框,

public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference

preference) {

if (preference instanceof AccessPointPreference) {

AccessPointState state = ((AccessPointPreference)preference).getAccessPointState();

showAccessPointDialog(state, AccessPointDialog.MODE_INFO);

}

}

4. 连接

当用户在 AcessPointDialog 中选择好加密方式和输入密钥之后,再点击连接按钮,Android就会去连接这个 AP。

private void handleConnect() {

String password = getEnteredPassword();

if (!TextUtils.isEmpty(password)) {

mState.setPassword(password);

}

mWifiLayer.connectToNetwork(mState);

}

WifiLayer 会先检测这个 AP 是不是之前被配置过,这个是通过向 wpa_supplicant 发送LIST_NETWORK 命令并且比较返回值来实现的,

// Need WifiConfiguration for the AP

WifiConfiguration config = findConfiguredNetwork(state);

如果 wpa_supplicant 没有这个 AP 的配置信息, 则会向 wpa_supplicant 发送 ADD_NETWORK命令来添加该 AP,

if (config == null) {

// Connecting for the first time, need to create it

config = addConfiguration(state, ADD_CONFIGURATION_ENABLE|ADD_CONFIGURATION_SAVE);

}

ADD_NETWORK 命令会返回一个ID , WifiLayer再用这个返回的ID作为参数向wpa_supplicant 发送 ENABLE_NETWORK 命令,从而让 wpa_supplicant 去连接该 AP。

// Make sure that network is enabled, and disable others

mReenableApsOnNetworkStateChange = true;

if (!mWifiManager.enableNetwork(state.networkId, true)) {

Log.e(TAG, “Could not enable network ID ” + state.networkId);

error(R.string.error_connecting);

return false;

}

5. 配置 IP 地址

当 wpa_supplicant 成功连接上 AP 之后,它会向控制通道发送事件通知连接上 AP 了,从而wifi_wait_for_event 函数会接收到该事件,由此 WifiMonitor 中的 MonitorThread 会被执行来出来这个事件,

void handleEvent(int event, String remainder) {

case CONNECTED:

handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED,remainder);

break;

WifiMonitor 再调用 WifiStateTracker 的 notifyStateChange,WifiStateTracker 则接着会往自身发送 EVENT_DHCP_START 消息来启动 DHCP 去获取 IP 地址,

private void handleConnectedState() {

setPollTimer();

mLastSignalLevel = -1;

if (!mHaveIPAddress && !mObtainingIPAddress) {

mObtainingIPAddress = true;

mDhcpTarget.obtainMessage(EVENT_DHCP_START).sendToTarget();

}

}

然后再广播发送 NETWORK_STATE_CHANGED_ACTION 这个 Intent

case EVENT_NETWORK_STATE_CHANGED:

if (result.state != DetailedState.DISCONNECTED || !mDisconnectPending) {

intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);

intent.putExtra(WifiManager.EXTRA_NETWORK_INFO,mNetworkInfo);

if (result.BSSID != null)

intent.putExtra(WifiManager.EXTRA_BSSID, result.BSSID);

mContext.sendStickyBroadcast(intent);

}

}

break;

WifiLayer 注册了接收 NETWORK_STATE_CHANGED_ACTION 这个 Intent,所以它的相关处理函数 handleNetworkStateChanged 会被调用,

当 DHCP 拿到 IP 地址之后,会再发送 EVENT_DHCP_SUCCEEDED 消息,

private class DhcpHandler extends Handler {

public void handleMessage(Message msg) {

switch (msg.what) {

case EVENT_DHCP_START:

if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {

event = EVENT_DHCP_SUCCEEDED;

}

WifiLayer 处 理 EVENT_DHCP_SUCCEEDED 消息, 会再次广播发送NETWORK_STATE_CHANGED_ACTION 这个Intent,这次带上完整的IP 地址信息。

case EVENT_DHCP_SUCCEEDED:

mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);

setDetailedState(DetailedState.CONNECTED);

intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);

intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);

mContext.sendStickyBroadcast(intent);

break;

至此为止,整个连接过程完成。

Android wifi

本文档分析了Android的WIFI功能代码,对Android的WIFI功能实现起到一定的参考作用。

在Linux中,wlan(无线局域网)设备驱动是网络设备,使用网络接口。Wlan 在用户空间使用标准的socket 接口进行控制。

内核的移植(wifi驱动的加载):

一、WIFI 协议和驱动程序在内核进行 menuconfig 配置时,配置选项为: 1、“networking support ”>"wireless".

2.、“device drivers” > "network device support" >"wireless LAN"

二、android wifi 基本架构

JAVA应用层 Setting、WifiSwitcher等应用

上下的通讯为Binder机制

JAVA框架层

Wifi manager

Wifi service

上下通讯为JNI

C/C++ 框架层

Wifi的JNI

WPA适配层

Wpa_supplicant程序

内核空间 Wifi的内核驱动程序

5. WIFI中间层的运行解析

1、android下如何通过jni监控wifi网络连接、dhcpcd执行和power电源控制

libs/android_runtime/android_net_wifi_Wifi.Cpp

部分jni接口

static JNINativeMethod gWifiMethods[] = {

{ "loadDriver", "()Z", (void *)android_net_wifi_loadDriver },

{ "setPowerModeCommand", "(I)Z", (void*) android_net_wifi_setPowerModeCommand },//电源管理

{ "connectToSupplicant", "()Z", (void *)android_net_wifi_connectToSupplicant },

{ "waitForEvent", "()Ljava/lang/String;", (void*) android_net_wifi_waitForEvent },

{ "disconnectCommand", "()Z", (void *)android_net_wifi_disconnectCommand },

...

};

int register_android_net_wifi_WifiManager(JNIEnv* env)

{

...

return AndroidRuntime::registerNativeMethods(env,

WIFI_PKG_NAME, gWifiMethods, NELEM(gWifiMethods));//登记jni

}

libs/android_runtime/AndroidRuntime.cpp

static const RegJNIRec gRegJNI[] = {

...

REG_JNI(register_android_net_wifi_WifiManager),

...

};

int AndroidRuntime::startReg(JNIEnv* env)

{

...

register_jni_procs(gRegJNI, NELEM(gRegJNI), env);

...

}

AndroidRuntime::start

=>startReg(env)即调用方法int AndroidRuntime::startReg(JNIEnv* env)

==============================================================================

wifi_load_driver

wifi_start_supplicant

=>ensure_config_file_exists

//检查/data/misc/wifi/wpa_supplicant.conf文件是否存在,如果不存在,那么从/system/etc/wifi/wpa_supplicant.conf动态拷贝一份

android_net_wifi_connectToSupplicant

=>wifi_connect_to_supplicant

=>

ctrl_conn = wpa_ctrl_open(ifname);

monitor_conn = wpa_ctrl_open(ifname);

wpa_ctrl_attach(monitor_conn);

android_net_wifi_waitForEvent

=>wifi_wait_for_event

=>wpa_ctrl_recv(monitor_conn, buf, &nread);

=>recv(ctrl->s, reply, *reply_len, 0);//阻塞等待wpa_supplicant的netlink数据过来

=>如果接收的buf数据区,buf[0]为'<',那么说明有level级别信息,所以将'<'...'>'数据剔除,然后wifi_wait_for_event函数返回[luther.gliethttp].

java/android/android/net/wifi/WifiMonitor.java

public class WifiMonitor {

...

public void startMonitoring() {

new MonitorThread().start();//启动java线程

}

class MonitorThread extends Thread {

public MonitorThread() {

super("WifiMonitor");

}

public void run() {

for (;;) {

ensureSupplicantConnection();//=>WifiNative.connectToSupplicant调用jni函数android_net_wifi_connectToSupplicant

String eventStr = WifiNative.waitForEvent();//=>调用jni函数android_net_wifi_waitForEvent

//private static final int CONNECTED = 1;

//private static final int DISCONNECTED = 2;

//private static final String eventPrefix = "CTRL-EVENT-";

//private static final int eventPrefixLen = eventPrefix.length();

//private static final String connectedEvent = "CONNECTED";

//private static final String disconnectedEvent = "DISCONNECTED";

String eventName = eventStr.substring(eventPrefixLen);//去掉"CTRL-EVENT-"字符串

int nameEnd = eventName.indexOf(' ');//找到随后的空格位置,这在wpa_supplicant发送时

//#define WPA_EVENT_CONNECTED "CTRL-EVENT-CONNECTED "中,已经内置空格了.

if (nameEnd != -1)

eventName = eventName.substring(0, nameEnd);

int event;

if (eventName.equals(connectedEvent))//检测netlink过来的字符串action类型

event = CONNECTED;

else if (eventName.equals(disconnectedEvent))

event = DISCONNECTED;

...

int ind = eventStr.indexOf(" - ");//CTRL-EVENT-CONNECTED - Connection to ...

if (ind != -1)

eventData = eventStr.substring(ind + 3);

//剔除前导控制字符,将" - "后面的描述字符串作为真实数据,继续处理

...

if (event == STATE_CHANGE) {

handleSupplicantStateChange(eventData);

} else if (event == DRIVER_STATE) {

handleDriverEvent(eventData);

} else {

handleEvent(event, eventData);//对于CONNECTED和DISCONNECTED等netlink事件将执行此操作来处理[luther.gliethttp]

// If supplicant is gone, exit the thread

if (event == TERMINATING) {

break;

}

}

...

void handleEvent(int event, String remainder) {

switch (event) {

case DISCONNECTED:

handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder);

break;

case CONNECTED:

handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder);//控制界面显示

break;

...

}

public class WifiStateTracker extends NetworkStateTracker {

...

public void startEventLoop() {

mWifiMonitor.startMonitoring();//启动上面的MonitorThread线程

}

...

}

java/services/com/android/server/WifiService.java

public class WifiService extends IWifiManager.Stub {

...

private boolean setWifiEnabledBlocking(boolean enable) {

final int eventualWifiState = enable ? WIFI_STATE_ENABLED : WIFI_STATE_DISABLED;

...

if (enable) {

if (WifiNative.loadDriver()) {

Log.e(TAG, "Failed to load Wi-Fi driver.");

updateWifiState(WIFI_STATE_UNKNOWN);

return false;

}

if (WifiNative.startSupplicant()) {

WifiNative.unloadDriver();

Log.e(TAG, "Failed to start supplicant daemon.");

updateWifiState(WIFI_STATE_UNKNOWN);

return false;

}

mWifiStateTracker.startEventLoop();

//启动MonitorThread线程,等待wpa_supplicant将netlink数据转发过来,然后根据netlink动作类型,进一步影响界面显示[luther.gliethttp].

}

...

}

java/android/android/net/wifi/WifiStateTracker.java

电源管理

private void handleConnectedState() {

...

mDhcpTarget.obtainMessage(EVENT_DHCP_START).sendToTarget();//传递到下面的handleMessage方法

...

}

public void onChange(boolean selfChange) {

...

handleConnectedState();

...

}

public class WifiStateTracker extends NetworkStateTracker {

...

public void handleMessage(Message msg) {

switch (msg.what) {

case EVENT_SUPPLICANT_CONNECTION:

case EVENT_NETWORK_STATE_CHANGED:

handleConnectedState();//调用

...

private class DhcpHandler extends Handler {

private Handler mTarget;

public DhcpHandler(Looper looper, Handler target) {

super(looper);

mTarget = target;

}

public void handleMessage(Message msg) {

int event;

//private static final int DRIVER_POWER_MODE_AUTO = 0;

//private static final int DRIVER_POWER_MODE_ACTIVE = 1;

switch (msg.what) {

case EVENT_DHCP_START:

synchronized (this) {

WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_ACTIVE);//设置电源模式,调用android_net_wifi_setPowerModeCommand

}

Log.d(TAG, "DhcpHandler: DHCP request started");

//libs/android_runtime/android_net_NetUtils.cpp

//static JNINativeMethod gNetworkUtilMethods[] = {

//{ "runDhcp", "(Ljava/lang/String;Landroid/net/DhcpInfo;)Z", (void *)android_net_utils_runDhcp },

// ...

//};

if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {//执行dhcp申请ip地址操作

event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;

if (LOCAL_LOGD) Log.v(TAG, "DhcpHandler: DHCP request succeeded");

} else {

event = EVENT_INTERFACE_CONFIGURATION_FAILED;

Log.i(TAG, "DhcpHandler: DHCP request failed: " +

NetworkUtils.getDhcpError());

//如果dhcpcd分配ip失败,那么Message.obtain(mTarget, event).sendToTarget();将执行

//WifiNative.disconnectCommand();即:static JNINativeMethod gWifiMethods[] = {

//android_net_wifi_disconnectCommand发送"DISCONNECT"字符串[luther.gliethttp]

//然后在wpa_supplicant服务端执行wpa_supplicant_ctrl_iface_process

//wpa_supplicant_disassociate

}

synchronized (this) {

WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_AUTO);

}

Message.obtain(mTarget, event).sendToTarget();

break;

}

}

}

...

/**

* Send the tracker a notification that a connection to the supplicant

* daemon has been established.

*/

//在上面的public class WifiMonitor=>ensureSupplicantConnection

//=>

//while (!supplicantConnected) {

// boolean connected;

//synchronized (mWifiStateTracker) {

//connected = WifiNative.connectToSupplicant();//如果没有连接成功,那么while循环尝试,直到尝试成功,或者定义了oneShot,仅一次尝试

//=>mWifiStateTracker.notifySupplicantConnection();//如果WifiNative.connectToSupplicant()成功,那么将执行

//mWifiStateTracker.notifySupplicantConnection();的调用.

void notifySupplicantConnection() {//向对象发送message

Message.obtain(this, EVENT_SUPPLICANT_CONNECTION).sendToTarget();

}

void notifyStateChange(SupplicantState newState) {

Message.obtain(this, EVENT_SUPPLICANT_STATE_CHANGED, newState).sendToTarget();

}

...

}

static jboolean android_net_wifi_setPowerModeCommand(JNIEnv* env, jobject clazz, jint mode)

{

char cmdstr[256];

sprintf(cmdstr, "DRIVER POWERMODE %d", mode);

return doBooleanCommand(cmdstr, "OK");

}

android_net_wifi_setPowerModeCommand

=>doBooleanCommand

=>doCommand

=>wifi_command

=>wifi_send_command

=>wpa_ctrl_request

=>send给wpa_supplicant

然后wpa_supplicant将做如下接收操作:

system/extra/wpa_supplicant/main.c

=>wpa_supplicant_add_iface

=>wpa_supplicant_init_iface2

=>wpa_supplicant_ctrl_iface_init

=>注册ctrl_conn控制端口和monitor_conn监听端口的处理函数

eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive, wpa_s, priv);//ctrl_conn端口的handler处理函数

wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);//monitor_conn端口的回调处理函数,处理netlink数据到所有monitor_conn监听端口

=>wpa_supplicant_ctrl_iface_receive//对于unix通信方式

=>wpa_supplicant_ctrl_iface_process

=>如果wpa_cli发送的是wpa_cli driver xxx形式的命令,那么调用这个函数

if (os_strncmp(buf, "DRIVER ", 7) == 0) {//掠过前7个,直接将命令传过去

reply_len = wpa_supplicant_driver_cmd(wpa_s, buf + 7, reply, reply_size);

=>wpa_supplicant_driver_cmd

=>wpa_drv_driver_cmd

=>自定义DRIVER扩展处理函数,所以对于java传递过来的power电源管理命令,wpa_drv_driver_cmd将收到"POWERMODE 0"或者"POWERMODE 1"字符串[luther.gliethttp]

===========================================================================================

jni

=>runDhcp

=>android_net_utils_runDhcp

libs/netutils/dhcp_utils.c

=>dhcp_do_request

=>

static const char DAEMON_NAME[] = "dhcpcd";

static const char DAEMON_PROP_NAME[] = "init.svc.dhcpcd";

static const char DHCP_PROP_NAME_PREFIX[] = "dhcp";

const char *ctrl_prop = "ctl.start";

const char *desired_status = "running";

snprintf(result_prop_name, sizeof(result_prop_name), "%s.%s.result",

DHCP_PROP_NAME_PREFIX,

interface);

property_set(result_prop_name, "");//设置dhcp.eth0.result="";等到成功完成dhcp之后,

property_set(ctrl_prop, DAEMON_NAME);//向名字为dhcpcd的service,发送"ctrl.start"启动命令字,该service在init.rc中

//init.rc中dhcpcd服务进程命令字

//service dhcpcd /system/bin/dhcpcd eth0

// disabled

// oneshot

wait_for_property(DAEMON_PROP_NAME, desired_status, 10);

//init.c=>init进程

//=>handle_property_set_fd因为是"ctrl.start"命令字,所以调用handle_control_message处理控制信息

//=>handle_control_message

//=>msg_start

//=>

// struct service *svc = service_find_by_name(name);

// service_start(svc);//启动svc,即执行:/system/bin/dhcpcd eth0

//=>service_start

//=>pid = fork();

// if(pid == 0)execve(svc->args[0], (char**) svc->args, (char**) ENV);子进程执行execve运行/system/bin/dhcpcd,参数为eth0

//=>否则父进程,即init进程将

//=>notify_service_state(svc->name, "running");设置该svc的状态prop

// snprintf(pname, sizeof(pname), "init.svc.%s", name);

// property_set(pname, state);//所以这样上面wait_for_property(DAEMON_PROP_NAME, desired_status, 10);也才能够正常pass[luther.gliethttp].

wait_for_property(result_prop_name, NULL, 15);//等待dhcp.eth0.result=非空

===========================================================================================

system/extra/dhcpcd-4.0.0-beta9/dhcpcd.c

dhcpcd

=>main

# define SYSCONFDIR "/system/etc/dhcpcd"

#define PACKAGE "dhcpcd"

# define CONFIG SYSCONFDIR "/" PACKAGE ".conf"

# define LIBEXECDIR "/system/etc/dhcpcd"

# define SCRIPT LIBEXECDIR "/" PACKAGE "-run-hooks"

=>strlcpy(options->script, SCRIPT,sizeof(options->script));//默认的options->script="/system/etc/dhcpcd/dhcpcd-run-hooks"

=>f = fopen(cf ? cf : CONFIG, "r");//如果没有指定.conf文件,那么使用默认.conf文件

=>parse_config_line//解析"/system/etc/dhcpcd/dhcpcd.conf"默认配置文件

=>parse_option

=>如果在"/system/etc/dhcpcd/dhcpcd.conf"有"script"这个节

=>那么执行strlcpy(options->script, oarg, sizeof(options->script));直接拷贝

/*

{"script", required_argument, NULL, 'c'},

{"option", required_argument, NULL, 'o'},

"/system/etc/dhcpcd/dhcpcd.conf"中的部分内容如下:

...

option domain_name_servers, domain_name, domain_search, host_name

...

*/

=>dhcp_run

=>handle_dhcp_packet

=>handle_dhcp

=>bind_dhcp

reason = "TIMEOUT";reason = "BOUND";reason = "REBIND";reason = "RENEW";

system/extra/dhcpcd-4.0.0-beta9/configure.c

=> configure(iface, reason, state->new, state->old, &state->lease, options, 1);

//如果dhcp超时或者dhcp成功,都会调用exec_script来执行脚本,

//执行setprop dhcp.${interface}.result "failed"或者

//执行setprop dhcp.${interface}.result "ok"

=>exec_script(options, iface->name, reason, NULL, old);

=>然后configure_env通过环境变量将reason传递到脚本中

int exec_script(const struct options *options, const char *iface, const char *reason,

const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo)

=>pid = fork();

=>if(pid == 0)execve(options->script, argv, env);//子进程执行脚本,默认"/system/etc/dhcpcd/dhcpcd-run-hooks"

//dhcpcd-run-hooks脚本会根据level值,决定是否执行system/etc/dhcpcd/dhcpcd-hook/*目录下的相应文件

//我们的系统在该system/etc/dhcpcd/dhcpcd-hook/*目录下有如下3个文件

//95-configured

//20-dns.conf

//01-test

=>父进程返回while (waitpid(pid, &status, 0) == -1)等待子进程脚本执行完成

system/extra/dhcpcd-4.0.0-beta9/dhcpcd-hooks/20-dns.conf

system/extra/dhcpcd-4.0.0-beta9/dhcpcd-hooks/95-configured

...

setprop dhcp.${interface}.ipaddress "${new_ip_address}"

setprop dhcp.${interface}.result "ok"//设置属性为ok

setprop dhcp.${interface}.result "failed"

...

===========================================================================================

inet_init、tcp_prot

sock->ops->sendmsg(iocb, sock, msg, size);

=>inetsw_array[]

=>inet_stream_ops

=>tcp_sendmsg

===========================================================================================

wpa_cli.c

=>main

=>wpa_cli_interactive

=>wpa_cli_recv_pending(monitor_conn, 0, 0);//阻塞等待wpa_supplicant发送数据过来

=>如果action_monitor为true,那么将执行一些简单加工操作,否则将直接将wpa_supplicant发过来的数据打印到console上[luther.gliethttp].

参考文献

网络资源:

http://osdir.com/ml/android-porting/2009-06/msg00714.html

http://osdir.com/ml/android-porting/2009-07/msg00303.html

http://bbs.imp3.net/redirect.php?tid=747705&goto=newpost

http://osdir.com/ml/android-porting/2010-02/msg00152.html

http://blog.chinaunix.net/u2/66024/showart_1926589.html

关于 wifi问题的处理

wifi porting文件和目录

porting wifi主要分为两个部分,源码的修改和配置文件的修改,其中配置文件的修改包括源码未编译时配置文件的修改和源码编译后的配置文件修改,下面就这两部分分析:

A:android未编译时的配置文件修改和源码修改

1:/android-cupcake/build/target/board/generic/ BoardConfig.mk

确定是否存在HAVE_CUSTOM_WIFI_DRIVER_2 := true,如果没有则添加该选项

/android-eclair/external/wpa_supplicant/.config.h

确定.config.h中,是否存在以下3个选项

CONFIG_WIRELESS_EXTENSION=y

CONFIG_CTRL_IFACE=y

CONFIG_DRIVER_WEXT=y

以上是支持wifi驱动的选项!

2:修改的源码文件

2.1/android-cupcake/system/wlan/ti/sta_dk_4_0_4_32/CUDK/tiwlan_loader/tiwlan_loader.c

这个文件修改的tiwlan_loader服务,这个服务在android1.5中需要返回成功,表示加载wifi的固件到eeprom中成功,而实际内核在加载wifi驱动的时候,同时加载了固件(即提供的bin文件)。但是在android2.0中,这个服务不是必须的!在编译tiwlan_loader.c时需要/android-cupcake/system/wlan/ti/sta_dk_4_0_4_32/CUDK/UtilityAdapter编译出来的库。

2.2/android-cupcake/hardware/libhardware_legacy/wifi/wifi.c

这个是porting wifi驱动的最重要的文件,其中包括驱动加载,连接wpa_supplicant服务都是在wifi.c中完成。所以要修改驱动加载的网络接口名和相关的宏。

2.3/android-cupcake/frameworks/base/wifi/java/android/net/wifi

这个目录下是wifi中相关的java代码,其中修改的WifiStateTracker.java,这个主要修改dhcp时,获取动态ip地址的网络接口名。

2.4 external/wpa_supplicant/ctrl_iface_unix.c

这个主要修改wpa_supplicatn连接时的权限,wpa_supplicant服务启动的时候客户端和服务端通过unixsocket通信,JAVA UI界面是通过此socket文件与驱动联系,此服务生成的socket在/data/system/wpa_supplicant/目录下,如果涉及到权限问题,则需要修改 external/wpa_supplicant/ctrl_iface_unix.c中相关的目录的权限


2.5 frameworks/base/services/java/com/android/server/WifiService.java

这个在android1.5中,上传到BSSID,ISSID,java代码无法识别。在android2.0中无需修改。


B:android编译后的相关配置文件的修改

3.1/system/etc/wifi/wpa_supplicant.conf

看目录下是否存在该文件,如果不存在,则添加;并且添加wpa_supplicant服务socket的服务接口,如下所示:

ctrl_interface=/data/system/wpa_supplicant//默认的mlan0无线网络接口的目录

update_config=1 //这个可能是更新的配置,但不确认

3.2/system/etc/dhcpcd/dhcpcd.conf


看是否存在改文件,不存在则添加,并且修改无线网络接口的网络名字,如android默认的是tiwlan0 ,而我的无线网络接口是mlan0,则把interface 后面的接口改成mlan0

3.3 init.rc


service wpa_supplicant /system/bin/wpa_supplicant -imlan0 -c/system/etc/wifi/wpa_supplicant.conf

disable

oneshot

service dhcpcd /system/bin/dhcpcd -d -f /system/etc/dhcpcd/dhcpcd.conf mlan0

disable

oneshot


以上是添加在wifi的服务。

mkdir /data/misc/wifi 0777 wifi wifi

mkdir /data/misc/wifi/sockets 0777 wifi wifi

mkdir /data/system/wpa_supplicant 0777 wifi wifi

mkdir /data/misc/dhcp 0777 dhcp dhcp

chown dhcp dhcp /data/misc/dhcp


新建以上的目录。

如果你不的平台不出稀奇古怪的问题的话,现在你已经可以ping通你想用的ip地址咯!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: