手机上的wifi功能,多半都被当做客户端在使用。当做热点共享网络时的场景比较少。 最近做一个尝试,将所有试图连接到Android便携热点的客户端的信息,通过底层一直上报上来,最终增加API供上层应用调用。 在原生的Android代码中,其实已经有一个WifiDevice类来表示当前连接至wifi热点的客户端信息,我们先来看一下这个类是怎样定义的。 /** * Describes information about a detected Wi-Fi STA. * {@hide} */ public class WifiDevice implements Parcelable { /** * The device MAC address is the unique id of a Wi-Fi STA */ public String deviceAddress = ""; /** * The device name is a readable string of a Wi-Fi STA */ public String deviceName = ""; /** * The device state is the state of a Wi-Fi STA */ public int deviceState = 0; /** * These definitions are for deviceState */ public static final int DISCONNECTED = 0; public static final int CONNECTED = 1; public static final int BLACKLISTED = 2; private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED"; private static final String AP_STA_DISCONNECTED_STR = "AP-STA-DISCONNECTED"; private static final String AP_STA_REPORT_STR = "AP-STA-REPORT"; /** {@hide} */ public WifiDevice() {} /** * @param string formats supported include * * AP-STA-CONNECTED 42:fc:89:a8:96:09 * AP-STA-DISCONNECTED 42:fc:89:a8:96:09 * * Note: The events formats can be looked up in the hostapd code * @hide */ public WifiDevice(String dataString) throws IllegalArgumentException { String[] tokens = dataString.split(" "); if (tokens.length < 2) { throw new IllegalArgumentException(); } if (tokens[0].indexOf(AP_STA_CONNECTED_STR) != -1) { deviceState = CONNECTED; } else if (tokens[0].indexOf(AP_STA_DISCONNECTED_STR) != -1) { deviceState = DISCONNECTED; }else if (tokens[0].indexOf(AP_STA_REPORT_STR) != -1) { deviceState = BLACKLISTED; } else { throw new IllegalArgumentException(); } deviceAddress = tokens[1]; } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof WifiDevice)) { return false; } WifiDevice other = (WifiDevice) obj; if (deviceAddress == null) { return (other.deviceAddress == null); } else { return deviceAddress.equals(other.deviceAddress); } } /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { dest.writeString(deviceAddress); dest.writeString(deviceName); dest.writeInt(deviceState); } /** Implement the Parcelable interface {@hide} */ public static final Creator<WifiDevice> CREATOR = new Creator<WifiDevice>() { public WifiDevice createFromParcel(Parcel in) { WifiDevice device = new WifiDevice(); device.deviceAddress = in.readString(); device.deviceName = in.readString(); device.deviceState = in.readInt(); return device; } public WifiDevice[] newArray(int size) { return new WifiDevice[size]; } }; }<span style="font-family:Microsoft YaHei;font-size:12px;"> </span>
比起其他复杂的类来说,WifiDevice.java非常简单,只有几个成员变量和方法。 它代表一个接入到wifi热点的客户端的信息,其中包括了客户端的MAC地址,主机名和接入状态。
一般情况下,我们需要的客户端信息也就是这几个内容了。
不过有一个问题,WifiDevice只能表示已经正常接入热点的客户端,而那些曾经试图连接热点,但是并没有连接成功的客户端呢(比如由于密码错误而连接失败的)?如果我们
希望能得到周边所有曾经和热点发生过关系(!?)的客户端的信息呢?如何获取这写信息呢?
先抛开以上的问题,我们先来了解一下,正常接入的客户端是如何获取它的信息的。那先得看一下Tethering.java(\frameworks\base\services\core\java\com\android\server\connectivity\Tethering.java)
tether这个词意思是拴绳,拴住的意思,这里可以理解成是分享的意思,比如 WIFI_TETHERING(用WIFI分享网路),
USB_TETHERING(用USB分享网络)。仔细阅读Tethering中的代码,可以看到,这个类主要就是
为网络共享服务的,其中包括USB共享,蓝牙共享和wifi共享。直接说Tethering,其实是跳过了connectivityservice的,上层获取客户端信息,其实是通过ConnectivityManager调用ConnectivityService的方法,最终
调用到Tethering的 getTetherConnectedSta() 方法。
public List<WifiDevice> getTetherConnectedSta() { Iterator it; List<WifiDevice> TetherConnectedStaList = new ArrayList<WifiDevice>(); if (mContext.getResources().getBoolean(com.android.internal.R.bool.config_softap_extention)) { it = mConnectedDeviceMap.keySet().iterator(); while(it.hasNext()) { String key = (String)it.next(); WifiDevice device = (WifiDevice)mConnectedDeviceMap.get(key); if (VDBG) { Log.d(TAG, "getTetherConnectedSta: addr=" + key + " name=" + device.deviceName); } TetherConnectedStaList.add(device); } } return TetherConnectedStaList; }
仔细看下,无非是在Tethering中定义了一个mConnectedDeviceMap成员,专门用来存放接入的客户端信息,以下是定义
private HashMap<String, WifiDevice> mConnectedDeviceMap = new HashMap<String, WifiDevice>();
有两个地方更新了这个HashMap。其实两个地方都是 一样的,一个是已经保存这个设备的IP信息的,另一个是如果没有保存过,则先启动一个DnsmasqThread线程,给客户端分配IP后,
再讲这个WifiDevice保存在HashMap中。总体来说就是在第一个方法interfaceMessageRecevied(String message)中保存了接入的客户端信息。
1.
private static class DnsmasqThread extends Thread { private final Tethering mTethering; private int mInterval; private int mMaxTimes; private WifiDevice mDevice; public DnsmasqThread(Tethering tethering, WifiDevice device, int interval, int maxTimes) { super("Tethering"); mTethering = tethering; mInterval = interval; mMaxTimes = maxTimes; mDevice = device; } public void run() { boolean result = false; try { while (mMaxTimes > 0) { result = mTethering.readDeviceInfoFromDnsmasq(mDevice); if (result) { if (DBG) Log.d(TAG, "Successfully poll device info for " + mDevice.deviceAddress); break; } mMaxTimes --; Thread.sleep(mInterval); } } catch (Exception ex) { result = false; Log.e(TAG, "Pulling " + mDevice.deviceAddress + "error" + ex); } if (!result) { if (DBG) Log.d(TAG, "Pulling timeout, suppose STA uses static ip " + mDevice.deviceAddress); } // When STA uses static ip, device info will be unavaiable from dnsmasq, // thus no matter the result is success or failure, we will broadcast the event. // But if the device is not in L2 connected state, it means the hostapd connection is // disconnected before dnsmasq get device info, so in this case, don't broadcast // connection event. WifiDevice other = mTethering.mL2ConnectedDeviceMap.get(mDevice.deviceAddress); if (other != null && other.deviceState == WifiDevice.CONNECTED) { mTethering.mConnectedDeviceMap.put(mDevice.deviceAddress, mDevice); mTethering.sendTetherConnectStateChangedBroadcast(); } else { if (DBG) Log.d(TAG, "Device " + mDevice.deviceAddress + "already disconnected, ignoring"); } } }
2.
public void interfaceMessageRecevied(String message) { // if softap extension feature not enabled, do nothing if (!mContext.getResources().getBoolean(com.android.internal.R.bool.config_softap_extention)) { return; } if (DBG) Log.d(TAG, "interfaceMessageRecevied: message=" + message); try { WifiDevice device = new WifiDevice(message); if (device.deviceState == WifiDevice.CONNECTED) { mL2ConnectedDeviceMap.put(device.deviceAddress, device); // When hostapd reported STA-connection event, it is possible that device // info can't fetched from dnsmasq, then we start a thread to poll the // device info, the thread will exit after device info avaiable. // For static ip case, dnsmasq don't hold the device info, thus thread // will exit after a timeout. if (readDeviceInfoFromDnsmasq(device)) { mConnectedDeviceMap.put(device.deviceAddress, device); sendTetherConnectStateChangedBroadcast(); } else { if (DBG) Log.d(TAG, "Starting poll device info for " + device.deviceAddress); new DnsmasqThread(this, device, DNSMASQ_POLLING_INTERVAL, DNSMASQ_POLLING_MAX_TIMES).start(); } } else if (device.deviceState == WifiDevice.DISCONNECTED) { mL2ConnectedDeviceMap.remove(device.deviceAddress); mConnectedDeviceMap.remove(device.deviceAddress); sendTetherConnectStateChangedBroadcast();
。。。。。。。
interfaceMessageRecevied(String message)中,很明显是入参message中携带了客户端信息,这个信息由谁发送,看一下它的调用关系。NetworkManagementService.java中的
notifyInterfaceMessage(String message)调用了它。
/** * Notify our observers of a change in the data activity state of the interface */ private void notifyInterfaceMessage(String message) { final int length = mObservers.beginBroadcast(); for (int i = 0; i < length; i++) { try { mObservers.getBroadcastItem(i).interfaceMessageRecevied(message); } catch (RemoteException e) { } catch (RuntimeException e) { } } mObservers.finishBroadcast(); }
而它的上一级调用关系是在NetworkManagementService中的NetdCallbackReceiver接收到Event事件后,根据事件的类型来逐个处理的。
case NetdResponseCode.InterfaceMessage: /* * An message arrived in network interface. * Format: "NNN IfaceMessage <3>AP-STA-CONNECTED 00:08:22:64:9d:84 */ if (cooked.length < 3 || !cooked[1].equals("IfaceMessage")) { throw new IllegalStateException(errorMessage); } Slog.d(TAG, "onEvent: "+ raw); if(cooked[4] != null) { notifyInterfaceMessage(cooked[3] + " " + cooked[4]); } else { notifyInterfaceMessage(cooked[3]); } return true; // break;
看上面,是接收到InterfaceMessage消息后,进行了消息处理,消息格式应该是注释中的格式“NNN IfaceMessage <3>AP-STA-CONNECTED 00:08:22:64:9d:84”,
InterfaceMessage消息又是从何而来呢?
这块直接跳到HAL层了,Netd进程,不知道你是否有所了解,我之前也小小研究过一下,不过仅限于使用,因为之前做过Android系统的数据卡,启动软AP时,都是直接
使用Netd和Softap等命令了,很好用。不过 没有更深入的了解,,后面有机会再好好学习一下。此处消息的传递,就是,来源于softapcontroller。
void *SoftapController::threadStart(void *obj){ SoftapController *me = reinterpret_cast<SoftapController *>(obj); struct wpa_ctrl *ctrl; int count = 0; ALOGD("SoftapController::threadStart..."); DIR *dir = NULL; dir = opendir(HOSTAPD_SOCKETS_DIR); if (NULL == dir && errno == ENOENT) { mkdir(HOSTAPD_SOCKETS_DIR, S_IRWXU|S_IRWXG|S_IRWXO); chown(HOSTAPD_SOCKETS_DIR, AID_WIFI, AID_WIFI); chmod(HOSTAPD_SOCKETS_DIR, S_IRWXU|S_IRWXG); } else { if (dir != NULL) { /* Directory already exists */ ALOGD("%s already exists", HOSTAPD_SOCKETS_DIR); closedir(dir); } if (errno == EACCES) ALOGE("Cant open %s , check permissions ", HOSTAPD_SOCKETS_DIR); } chmod(HOSTAPD_DHCP_DIR, S_IRWXU|S_IRWXG|S_IRWXO); ctrl = wpa_ctrl_open(HOSTAPD_UNIX_FILE); while (ctrl == NULL) { /* * Try to connect to hostapd via wpa_ctrl interface. * During conneciton process, it is possible that hostapd * has station connected to it. * Set sleep time to a appropriate value to lower the * ratio that miss the STA-CONNECTED msg from hostapd */ usleep(20000); ctrl = wpa_ctrl_open(HOSTAPD_UNIX_FILE); if (ctrl != NULL || count >= 150) { break; } count ++; } if (count == 150 && ctrl == NULL) { ALOGE("Connection to hostapd Error."); return NULL; } if (wpa_ctrl_attach(ctrl) != 0) { wpa_ctrl_close(ctrl); ALOGE("Attach to hostapd Error."); return NULL; } while(me->mHostapdFlag) { int res = 0; char buf[256]; char dest_str[300]; while (wpa_ctrl_pending(ctrl)) { size_t len = sizeof(buf) - 1; res = wpa_ctrl_recv(ctrl, buf, &len); if (res == 0) { buf[len] = '\0'; ALOGD("Get event from hostapd (%s)", buf); memset(dest_str, 0x0, sizeof(dest_str)); snprintf(dest_str, sizeof(dest_str), "IfaceMessage active %s", buf); me->mSpsl->sendBroadcast(ResponseCode::InterfaceMessage, dest_str, false); } else { break; } } if (res < 0) { break; } sleep(2); } wpa_ctrl_detach(ctrl); wpa_ctrl_close(ctrl); return NULL; }
此处是怎样通信的,我还没有搞的彻底明白,不过并不影响我们理解整个流程,总是肯定是通过socket之类的方式,将InterfaceMessage消息发送出去,
消息中所携带的字符串保存在"dest_str"中。如果你抓取一个logcat的log,就可以看到消息字符串的格式为“IfaceMessage active <3>AP-STA-CONNECTED 00:0a:f5:8a:be:58”,
可以和前面讲的消息格式对应上。
再往前追溯一下,softapcontroller的消息来自于hostapd, (ps. hostapd的知识又是一个比较大的分支了,此处略去,可以把它理解成驱动和上层承上启下的一个枢纽,它可以接收
来自wifi驱动的底层消息,处理后分门别类的通知给上层)。其他的不多说,消息是从sta_info.c中的ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta, int authorized)
发出的。这里面主要是发送connect和disconnect消息的,如果sta->flags & WLAN_STA_AUTHORIZED,就是说如果鉴权过了话,就发送connect, 没有过就发送disconnect消息。
void ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta, int authorized) { const u8 *dev_addr = NULL; char buf[100]; #ifdef CONFIG_P2P u8 addr[ETH_ALEN]; #endif /* CONFIG_P2P */ if (!!authorized == !!(sta->flags & WLAN_STA_AUTHORIZED)) return; #ifdef CONFIG_P2P if (hapd->p2p_group == NULL) { if (sta->p2p_ie != NULL && p2p_parse_dev_addr_in_p2p_ie(sta->p2p_ie, addr) == 0) dev_addr = addr; } else dev_addr = p2p_group_get_dev_addr(hapd->p2p_group, sta->addr); #endif /* CONFIG_P2P */ if (dev_addr) os_snprintf(buf, sizeof(buf), MACSTR " p2p_dev_addr=" MACSTR, MAC2STR(sta->addr), MAC2STR(dev_addr)); else os_snprintf(buf, sizeof(buf), MACSTR, MAC2STR(sta->addr)); if (authorized) { wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_CONNECTED "%s", buf); if (hapd->msg_ctx_parent && hapd->msg_ctx_parent != hapd->msg_ctx) wpa_msg_no_global(hapd->msg_ctx_parent, MSG_INFO, AP_STA_CONNECTED "%s", buf); sta->flags |= WLAN_STA_AUTHORIZED; } else { wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_DISCONNECTED "%s", buf); if (hapd->msg_ctx_parent && hapd->msg_ctx_parent != hapd->msg_ctx) wpa_msg_no_global(hapd->msg_ctx_parent, MSG_INFO, AP_STA_DISCONNECTED "%s", buf); sta->flags &= ~WLAN_STA_AUTHORIZED; } if (hapd->sta_authorized_cb) hapd->sta_authorized_cb(hapd->sta_authorized_cb_ctx, sta->addr, authorized, dev_addr); }
一开始只知道此处会有消息发送,但是不知道是怎样发送了,仔细看了一下wpa_msg()函数的原型,才搞明白了。
void wpa_msg(void *ctx, int level, const char *fmt, ...)
{
va_list ap;
char *buf;
int buflen;
int len;
char prefix[130];
va_start(ap, fmt);
buflen = vsnprintf(NULL, 0, fmt, ap) + 1;
va_end(ap);
buf = os_malloc(buflen);
if (buf == NULL) {
wpa_printf(MSG_ERROR, "wpa_msg: Failed to allocate message "
"buffer");
return;
}
wpa_printf(MSG_DEBUG, "@@@@wpa_msg");
va_start(ap, fmt);
prefix[0] = ‘\0‘;
if (wpa_msg_ifname_cb) {
const char *ifname = wpa_msg_ifname_cb(ctx);
if (ifname) {
int res = os_snprintf(prefix, sizeof(prefix), "%s: ",
ifname);
if (res < 0 || res >= (int) sizeof(prefix))
prefix[0] = ‘\0‘;
}
}
len = vsnprintf(buf, buflen, fmt, ap);
va_end(ap);
wpa_printf(level, "%s%s", prefix, buf);
if (wpa_msg_cb){
wpa_printf(MSG_DEBUG, "@@@@wpa_msg_cb");
wpa_msg_cb(ctx, level, 0, buf, len);
}
os_free(buf);
}
注意后面全局变量wpa_msg_cb, 在hostapd_ctrl_iface_init(struct hostapd_data *hapd)中,初始化的时候,可以看到这么一行代码:
hapd->msg_ctx = hapd;
wpa_msg_register_cb(hostapd_ctrl_iface_msg_cb);
这里注册了一个回调hostapd_ctrl_iface_msg_cb(void *ctx, int level, int global, const char *txt, size_t len),这函数调用了hostapd_ctrl_iface_send(.....)。
而这个和wpa_msg_cb有什么关系呢?那你就得看看wpa_msg_register_cb了,原来它是将注册的回调函数指针赋给了wpa_msg_cb,那么wpa_msg_cb相当于
最终调用了hostapd_ctrl_iface_send用于发送消息啦。
void wpa_msg_register_cb(wpa_msg_cb_func func)
{
wpa_msg_cb = func;
}
以上就是整个framework接收到接入客户端连接成功,并且获取客户端信息的流程,不过我原本看的时候,是从下往上看的,先看了hostapd发送消息的部分,再看的上层。