android入门-----dhcp服务(上)
Posted 超人博客屋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android入门-----dhcp服务(上)相关的知识,希望对你有一定的参考价值。
一、了解一下dhcp协议
以下是dhcp的报文结构并附上一个discoverry请求包对应着理解。
DHCP协议流程
以下是dhcp客户端请求流程及对应的抓包截图
二、dhcpcd的启动
DHCP服务器与dhcpcd的交互协议之前已经简单说过了,这里主要介绍android系统中dhcp的使用,dhcpcd作为DHCP服务器的客户端又作为android系统的服务端存在于android系统,其源码位于external/dhcpcd下,根据android.mk可以看出,编译生成的一个dhcpcd执行文件,放在/system/bin下。下面我们来看看它又是如何与属性服务关联的。
android属性服务是由init创建的,大家对windows的注册表这个东东有了解的话,就容易理解android的属性服务,一般,系统或应用程序会把自己的一些属性存储在注册表中,即使系统重启或应用重启,它能根据之前在注册表中设置的属性,进行相应的初始化。android提供这个功能就叫做属性服务(property service)。应用程序可通过这个属性机制查询或设置属性,也可以启动或停止一些服务。属性服务会在另一篇讲android启动的时候详细说明,这里只简单的介绍一下。属性服务由init创建,其本身是创建共享内存用于IPC共享,属性服务中的属性及服务来自default.prop,/system/build.prop,/system/default.prop,/data/local.prop和***.rc,属性服务对外是通过socket通信,客户端设置或操作属性服务中的属性或服务,也是通过soket来完成的。
现在回归到dhcpcd的启动,首先dhcpcd是要注册到属性服务中,android系统通过属性服务控制dhcpcd的启动。注册属性服务就要看***.rc文件 例如
service dhcpcd_eth0 /system/bin/dhcpcd -ABKLG
class main
disabled
oneshot
系统(libnetutils)通过property_set(ctrl_prop, DAEMON_NAME);//向名字为dhcpcd的属性service,发送"ctrl.start"启动命令字 来启动dhcpcd,用"ctl.stop"停止命令停止dhcpcd。dhcpcd获得的资源会通过脚本设置到属性中。
三、dhcpcd的配置
dhcpcd的配置,一般是启动的时候跟上参数或配置文件完成的,譬如/system/bin/dhcpcd -ABKL -f dhcpcd.conf或/system/bin/dhcpcd -n等等,参数的定义晚上都能查到,这里就简单的过一下
dhcpcd [-bdeknpqABDEGKLTV] [-c, --script script] [-f, --config file]
[-h, --hostname hostname] [-i, --vendorclassid vendorclassid]
[-l, --leasetime seconds] [-m, --metric metric]
[-o, --option option] [-r, --request address]
[-s, --inform address[/cidr]] [-t, --timeout seconds]
[-u, --userclass class] [-v, --vendor code, value]
[-y, --reboot seconds] [-z, --allowinterfaces pattern]
[-C, --nohook hook] [-F, --fqdn FQDN] [-I, --clientid clientid]
[-O, --nooption option] [-Q, --require option]
[-S, --static value] [-X, --blacklist address[/cidr]]
[-Z, --denyinterfaces pattern] [interface] [...]
dhcpcd -k, --release [interface]
dhcpcd -x, --exit [interface]
四、dhcpcd在android中数据传递流程
1、封装到netcfg中,可以使用netcfg+参数(netcfg dhcp eth0,netcfg up eth0等)使用。
从system\core\netcfg下的android.mk可以看出编译生成netcfg命令,而netcfg的功能都在netcfg.c中有一个结构体数组来定义如
struct
{
const char *name;
int nargs;
void *func;
} CMDS[] = {
{ "dhcp", 1, do_dhcp },
{ "up", 1, ifc_up },
{ "down", 1, ifc_down },
{ "deldefault", 1, ifc_remove_default_route },
{ "hwaddr", 2, set_hwaddr },
{ 0, 0, 0 },
};
这里调用netcfg dhcp就会相应的调用到do_dhcp(1为参数是一个譬如eth0)->dhcp_init_ifc->open_raw_socket,拿到socket的fd调用poll等待激活或超时后填充msg(init_dhcp_discover_msg或init_dhcp_request_msg)->send_message->send_packet->sendmsg(s, &msghdr, 0)发送给DHCP服务器,而在这个循环里也接收receive_packet和解析decode_dhcp_msg,处理dhcp的回复包信息(ack,nak,offer等)。这里要指出流程dhcp_init_ifc这里就已经在libnetutils这个库中处理了,所以dhcp的所有处理包括android调用的都是在此库中完成的。
int dhcp_init_ifc(const char *ifname)
{
省略部分代码
s = open_raw_socket(ifname, hwaddr, if_index);
for (;;) {
r = poll(&pfd, 1, timeout);
if (r == 0) {
if (timeout >= TIMEOUT_MAX) {
printerr("timed out\n");
if ( info.type == DHCPOFFER ) {
printerr("no acknowledgement from DHCP server\nconfiguring %s with offered parameters\n", ifname);
return dhcp_configure(ifname, &info);
}
errno = ETIME;
close(s);
return -1;
}
timeout = timeout * 2;
transmit:
size = 0;
msg = NULL;
switch(state) {
case STATE_SELECTING:
msg = &discover_msg;
size = init_dhcp_discover_msg(msg, hwaddr, xid);
break;
case STATE_REQUESTING:
msg = &request_msg;
size = init_dhcp_request_msg(msg, hwaddr, xid, info.ipaddr, info.serveraddr);
break;
default:
r = 0;
}
if (size != 0) {
r = send_message(s, if_index, msg, size);
if (r < 0) {
printerr("error sending dhcp msg: %s\n", strerror(errno));
}
}
continue;
}
if (r < 0) {
if ((errno == EAGAIN) || (errno == EINTR)) {
continue;
}
return fatal("poll failed");
}
errno = 0;
r = receive_packet(s, &reply);
if (r < 0) {
if (errno != 0) {
ALOGD("receive_packet failed (%d): %s", r, strerror(errno));
if (errno == ENETDOWN || errno == ENXIO) {
return -1;
}
}
continue;
}
decode_dhcp_msg(&reply, r, &info);
if (state == STATE_SELECTING) {
valid_reply = is_valid_reply(&discover_msg, &reply, r);
} else {
valid_reply = is_valid_reply(&request_msg, &reply, r);
}
switch(state) {
case STATE_SELECTING:
if (info.type == DHCPOFFER) {
state = STATE_REQUESTING;
timeout = TIMEOUT_INITIAL;
xid++;
goto transmit;
}
break;
case STATE_REQUESTING:
if (info.type == DHCPACK) {
printerr("configuring %s\n", ifname);
close(s);
return dhcp_configure(ifname, &info);
} else if (info.type == DHCPNAK) {
printerr("configuration request denied\n");
close(s);
return -1;
} else {
printerr("ignoring %s message in state %d\n",
dhcp_type_to_name(info.type), state);
}
break;
}
}
close(s);
return 0;
}
2、封装为libnetutils库中,提供给jni(android_net_netutils)调用。
android中提供给java层的网络调用接口主要就是NetworkUtils类,其中封装了android_net_netutils的native方法,而jni的方法又是调用libnetutils提供的方法,而libnetutils才是android主要维护dhcp服务的交互。举一个启动dhcpcd的例子来说明一下
NetworkUtils.runDhcp(mIface, dhcpResults)直接调用JNI的native函数android_net_utils_runDhcp->android_net_utils_runDhcpCommon->::dhcp_do_request(nameStr, ipaddr, gateway, &prefixLength,dns, server, &lease, vendorInfo, domains, mtu)这里获取到ip信息利用jmethodID回调java层函数设置这些信息到java层的类中。
int dhcp_do_request(const char *interface,char *ipaddr,char *gateway,uint32_t *prefixLength,char *dns[],char *server,uint32_t *lease,char *vendorInfo,char *domain,char *mtu)
{
忽略一些代码
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,
p2p_interface);
DHCP_PROP_NAME_PREFIX,
p2p_interface); 拼接字符串result_prop_name=‘dhcp.eth0.result’
snprintf(daemon_prop_name, sizeof(daemon_prop_name), "%s_%s",
DAEMON_PROP_NAME,
p2p_interface); 拼接字符串daemon_prop_name=‘init.svc.dhcpcd_eth0’
property_set(result_prop_name, "");
if (property_get(HOSTNAME_PROP_NAME, prop_value, NULL) && (prop_value[0] != '\0'))
snprintf(daemon_cmd, sizeof(daemon_cmd), "%s_%s:-f %s -h %s %s", DAEMON_NAME,
p2p_interface, DHCP_CONFIG_PATH, prop_value, interface);
else
snprintf(daemon_cmd, sizeof(daemon_cmd), "%s_%s:-f %s %s", DAEMON_NAME,
p2p_interface, DHCP_CONFIG_PATH, interface);
拼接字符串daemon_cmd=‘dhcpcd_eth0:-f /system/etc/dhcpcd/dhcpcd.conf eth0’
memset(prop_value, '\0', PROPERTY_VALUE_MAX);
property_set(ctrl_prop, daemon_cmd);
if (wait_for_property(daemon_prop_name, desired_status, 10) < 0) {
snprintf(errmsg, sizeof(errmsg), "%s", "Timed out waiting for dhcpcd to start");
return -1;
}
if (wait_for_property(result_prop_name, NULL, 60) < 0) {
snprintf(errmsg, sizeof(errmsg), "%s", "Timed out waiting for DHCP to finish");
return -1;
}
if (!property_get(result_prop_name, prop_value, NULL)) {
/* shouldn't ever happen, given the success of wait_for_property() */
snprintf(errmsg, sizeof(errmsg), "%s", "DHCP result property was not set");
return -1;
}
if (strcmp(prop_value, "ok") == 0) {
char dns_prop_name[PROPERTY_KEY_MAX];
if (fill_ip_info(interface, ipaddr, gateway, prefixLength, dns,
server, lease, vendorInfo, domain, mtu) == -1) {
return -1;
}
return 0;
} else {
snprintf(errmsg, sizeof(errmsg), "DHCP result was %s", prop_value);
return -1;
}
}
这里说明上述几个拼接字符串的作用,daemon_prop_name是用来同步获取dhcpcd当前启动状态的,result_prop_name是通知dhcpcd获取到非NULL结果的属性,启动dhcpcd则使用了启动属性服务的流程property_set(ctrl_prop, daemon_cmd);以后的章节中会详细介绍属性服务的内容,这里只需知道启动属性服务只需传ctl.start+属性服务名称:参数即可(这里为ctl.start+dhcpcd_eth0:-f /system/etc/dhcpcd/dhcpcd.conf eth0),而停止一个属性服务为ctl.stop+属性服务名称,重启一个属性服务ctl.restart+属性服务名称。
启动dhcpcd属性服务后,就会调用dhcpcd的main函数,从而进入dhcpcd的工作流程。read_config(从配置文件中设置option)->parse_config_line->parse_option->add_options(从传入的参数中设置option)->之后创建了几个event(signal、socket、link_socket、ipv6rs_socket),添加到start_eloop中poll阻塞轮询->init_state->configure_interface->run_script(iface)以reason = "PREINIT"执行脚本->handle_carrier->handle_interface(1, ifname)->start_interface->start_discover->send_discover->send_message((struct interface *)arg, DHCP_DISCOVER, send_discover)->open_sockets中创建了另一个处理dhcp的event(handle_dhcp_packet->bind_interface它是更新reason状态的)->send_raw_packet->sendto发送DHCP_DISCOVER给DHCP服务器。而其中穿插着以reason值执行脚本,从而更新系统dhcp属性,脚本如下
if [[ $interface == p2p* ]]
then
intf=p2p
else
intf=$interface
fi
setprop dhcp.${intf}.reason "${reason}"
case "${reason}" in
BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT)
setprop dhcp.${intf}.ipaddress "${new_ip_address}"
setprop dhcp.${intf}.gateway "${new_routers%% *}"
setprop dhcp.${intf}.mask "${new_subnet_mask}"
setprop dhcp.${intf}.leasetime "${new_dhcp_lease_time}"
setprop dhcp.${intf}.server "${new_dhcp_server_identifier}"
setprop dhcp.${intf}.vendorInfo "${new_vendor_encapsulated_options}"
setprop dhcp.${intf}.mtu "${new_interface_mtu}"
setprop dhcp.${intf}.result "ok"
;;
EXPIRE|FAIL|IPV4LL|STOP|NOCARRIER)
setprop dhcp.${intf}.result "failed"
;;
RELEASE)
setprop dhcp.${intf}.result "released"
;;
esac
dhcpcd各个状态的跳转,这里就不赘述了,因为这篇是介绍android的调用,并不是一片介绍dhcpcd流程的文章,如果以后有机会,也会有专门一篇介绍dhcpcd流程的,根据reason,大概有这几种状态STATIC,IPV4LL,INFORM,TIMEOUT,TEST,RENEW,REBIND,REBOOT,BOUND。
如有那里不对欢迎留言指点,共同探讨,谢谢。
以上是关于android入门-----dhcp服务(上)的主要内容,如果未能解决你的问题,请参考以下文章
第一次使用Genymotion遇到的问题:for an unknown reson,VirtualBox DHCP has not assigned an IP address to virtual