A brief analysis of how to monitor wifi network connection, dhcpcd execution and power supply control through jni under Android
================================================== ================================================== =========
libs/android_runtime/android_net_wifi_Wifi.cpp
Part of jni interface
static JNINativeMethod gWifiMethods[] = {
{ "loadDriver", "()Z", (void *)android_net_wifi_loadDriver },
{ "setPowerModeCommand", "(I)Z", (void*) android_net_wifi_setPowerModeCommand },//Power management
{ "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));//Register 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) calls the method int AndroidRuntime::startReg(JNIEnv* env)
================================================== ================================================== =========
wifi_load_driver
wifi_start_supplicant
=>ensure_config_file_exists
//Check whether the /data/misc/wifi/wpa_supplicant.conf file exists. If it does not exist, then dynamically copy a copy from /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);//Blocking and waiting for the netlink data of wpa_supplicant to come over
=>If in the received buf data area, buf[0] is '<', it means there is level information, so the '<'...'>' data is removed, and then the wifi_wait_for_event function returns [luther.gliethttp].
java/android/android/net/wifi/WifiMonitor.java
public class WifiMonitor {
...
public void startMonitoring() {
new MonitorThread().start();//Start java thread
}
class MonitorThread extends Thread {
public MonitorThread() {
super("WifiMonitor");
}
public void run() {
for (;;) {
ensureSupplicantConnection();//=>WifiNative.connectToSupplicant calls the jni function android_net_wifi_connectToSupplicant
String eventStr = WifiNative.waitForEvent();//=>Call jni function 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);//Remove the "CTRL-EVENT-" string
int nameEnd = eventName.indexOf(' ');//Find the subsequent space position, which is when wpa_supplicant is sent
//#define WPA_EVENT_CONNECTED "CTRL-EVENT-CONNECTED" has built-in spaces.
if (nameEnd != -1)
eventName = eventName.substring(0, nameEnd);
int event;
if (eventName.equals(connectedEvent))//Detect the string action type from netlink
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);
//Remove the leading control characters, use the description string after "-" as real data, and continue processing
...
if (event == STATE_CHANGE) {
handleSupplicantStateChange(eventData);
} else if (event == DRIVER_STATE) {
handleDriverEvent(eventData);
} else {
handleEvent(event, eventData);//For netlink events such as CONNECTED and DISCONNECTED, this operation will be performed to handle [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);//Control interface display
break;
...
}
public class WifiStateTracker extends NetworkStateTracker {
...
public void startEventLoop() {
mWifiMonitor.startMonitoring();//Start the above MonitorThread thread
}
...
}
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();
//Start the MonitorThread thread, wait for wpa_supplicant to forward the netlink data, and then further affect the interface display according to the netlink action type [luther.gliethttp].
}
...
}
java/android/android/net/wifi/WifiStateTracker.java
Power management
private void handleConnectedState() {
...
mDhcpTarget.obtainMessage(EVENT_DHCP_START).sendToTarget(); // Pass to the handleMessage method below
...
}
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();//call
...
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);//Set the power mode and call 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)) {//Execute dhcp application for ip address operation
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());
//If dhcpcd fails to allocate IP, then Message.obtain(mTarget, event).sendToTarget(); will be executed
//WifiNative.disconnectCommand(); that is: static JNINativeMethod gWifiMethods[] = {
//android_net_wifi_disconnectCommand sends "DISCONNECT" string [luther.gliethttp]
//Then execute wpa_supplicant_ctrl_iface_process on the wpa_supplicant server
//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.
*/
//In the above public class WifiMonitor=>ensureSupplicantConnection
//=>
//while (!supplicantConnected) {
// boolean connected;
//synchronized (mWifiStateTracker) {
//connected = WifiNative.connectToSupplicant();//If the connection is not successful, then the while loop tries until the attempt is successful, or oneShot is defined, only one attempt
//=>mWifiStateTracker.notifySupplicantConnection();//If WifiNative.connectToSupplicant() succeeds, it will be executed
//Call of mWifiStateTracker.notifySupplicantConnection();.
void notifySupplicantConnection() {//Send message to the object
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 to wpa_supplicant
Then wpa_supplicant will do the following receiving operations:
system/extra/wpa_supplicant/main.c
=>wpa_supplicant_add_iface
=>wpa_supplicant_init_iface2
=>wpa_supplicant_ctrl_iface_init
=>Register the processing functions of ctrl_conn control port and monitor_conn listening port
eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive, wpa_s, priv);//handler processing function of ctrl_conn port
wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);//Callback processing function of monitor_conn port, processes netlink data to all monitor_conn listening ports
=>wpa_supplicant_ctrl_iface_receive//For unix communication method
=>wpa_supplicant_ctrl_iface_process
=>If wpa_cli sends a command in the form of wpa_cli driver xxx, then call this function
if (os_strncmp(buf, "DRIVER ", 7) == 0) {//Skip the first 7 and pass the command directly
reply_len = wpa_supplicant_driver_cmd(wpa_s, buf + 7, reply, reply_size);
=>wpa_supplicant_driver_cmd
=>wpa_drv_driver_cmd
=> Customize the DRIVER extension processing function, so for the power power management command passed by java, wpa_drv_driver_cmd will receive the "POWERMODE 0" or "POWERMODE 1" string [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, "");//Set dhcp.eth0.result=""; Wait until dhcp is successfully completed,
property_set(ctrl_prop, DAEMON_NAME);//Send the "ctrl.start" startup command word to the service named dhcpcd, which is in init.rc
//dhcpcd service process command word in init.rc
//service dhcpcd /system/bin/dhcpcd eth0
// disabled
// oneshot
wait_for_property(DAEMON_PROP_NAME, desired_status, 10);
//init.c=>init process
//=>handle_property_set_fd is the "ctrl.start" command word, so handle_control_message is called to process the control message.
//=>handle_control_message
//=>msg_start
//=>
// struct service *svc = service_find_by_name(name);
//service_start(svc);//Start svc, that is, execute: /system/bin/dhcpcd eth0
//=>service_start
//=>pid = fork();
// if(pid == 0)execve(svc->args[0], (char**) svc->args, (char**) ENV); the child process executes execve and runs /system/bin/dhcpcd, parameters for eth0
//=>Otherwise the parent process, that is, the init process will
//=>notify_service_state(svc->name, "running"); Set the state prop of the svc
// snprintf(pname, sizeof(pname), "init.svc.%s", name);
// property_set(pname, state);//So in this way, wait_for_property(DAEMON_PROP_NAME, desired_status, 10); can pass [luther.gliethttp] normally.
wait_for_property(result_prop_name, NULL, 15);//Waiting for dhcp.eth0.result=non-null
================================================== ================================================== =========
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));//Default options->script="/system/etc/dhcpcd/dhcpcd-run-hooks"
=>f = fopen(cf ? cf : CONFIG, "r");//If no .conf file is specified, use the default .conf file
=>parse_config_line//Parse the "/system/etc/dhcpcd/dhcpcd.conf" default configuration file
=>parse_option
=>If there is a "script" section in "/system/etc/dhcpcd/dhcpcd.conf"
=>Then execute strlcpy(options->script, oarg, sizeof(options->script)); copy directly
/*
{"script", required_argument, NULL, 'c'},
{"option", required_argument, NULL, 'o'},
Part of the content in "/system/etc/dhcpcd/dhcpcd.conf" is as follows:
...
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);
//If dhcp times out or dhcp succeeds, exec_script will be called to execute the script.
//Execute setprop dhcp.${interface}.result "failed" or
//Execute setprop dhcp.${interface}.result "ok"
=>exec_script(options, iface->name, reason, NULL, old);
=>Then configure_env passes the reason into the script through the environment variable
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);//The child process executes the script, default is "/system/etc/dhcpcd/dhcpcd-run-hooks"
//dhcpcd-run-hooks script will decide whether to execute the corresponding files in the system/etc/dhcpcd/dhcpcd-hook/* directory based on the level value
//Our system has the following 3 files in the system/etc/dhcpcd/dhcpcd-hook/* directory
//95-configured
//20-dns.conf
//01-test
=>The parent process returns while (waitpid(pid, &status, 0) == -1) and waits for the child process script execution to complete
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"//Set the attribute to 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);//Blocking and waiting for wpa_supplicant to send data
=>If action_monitor is true, some simple processing operations will be performed, otherwise the data sent by wpa_supplicant will be printed directly to the console [luther.gliethttp].