安卓Netd

文献

         深入理解Android:Wi-Fi、NFC和GPS卷(完整版) · 看云

术语

  • netd-socket-client。名称为“netd”的socket的client端,像NetworkManagementService。

netd是Android系统中专门负责网络管理和控制的后台daemon程序,其功能主要分三大块:

  • 设置防火墙(Firewall)、网络地址转换(NAT)、带宽控制、无线网卡软接入点(Soft Access Point)控制,网络设备绑定(Tether)等。当中包括常见的设置以太网IP。
  • Android系统中DNS信息的缓存和管理。
  • 网络服务搜索(Net Service Discovery,简称NSD)功能,包括服务注册(Service Registration)、服务搜索(Service Browse)和服务名解析(Service Resolve)等。

安卓Netd_第1张图片

安卓Netd_第2张图片

图1 netd

netd位于kernel层和framework层之间,是Android系统中网络相关消息和命令转发及处理的中枢模块。在Android,netd编写语言是C++,NetworkManagementService或NsdService是Java。

NetworkManagementService是Java实现的binder服务,但其它app可能并不是它的直接binder client。像设置以太网IP,中间还有一个binder服务:EthernetServiceImpl,它是NetworkManagementService的binder client,“设置”中的以太网设置则是它的binder client。

netd收发数据是通过一个命名为“netd”的socket(创建netd这个init service时创建),CommandListener是该socket的server端,framework层中的对象则是该socket的client端(netd-socket-client),像NetworkManagementService。netd工作可分为两部分。

  1. 接收并处理来自netd-socket-client发来的命令。这些命令有的只须在netd层执行,有的则须要发向kernel。每条命令都有个命令号(CmdNum)、都须要有应答,因而是双向。命令在netd流程:CommandListener::onDataAvailable --> CommandListener::dispatchCommand --> 根据哪种命令进入相应runCommand,像“interface”进入InterfaceCmd::runCommand --> runCommand执行命令,要访问kernel的访问kernel,访问kernel用的是Netdevice编程技术,和4个NetlinkHandler无关 --> SocketClient::sendMsg反馈应答,SocketClient是发出此个命令的netd-socket-client。
  2. 接收并解析来自kernel的UEvent消息,然后再转发给netd-socket-client去处理。这些UEvent往往和网络有关,这类消息单向发到client。用的是广播:NetlinkHandler::onDataAvailable --> NetlinkHandler::onEvent --> CommandListener::sendBroadcast。须要强调,这类消息不是发向“netd”这个socket,即CommandListener::onDataAvailable不会收到它们,netd只是用CommandListener把消息发送给正连着它的netd-socket-client。

对netd-socket-client来说,不论是通过SocketClient::sendMsg单独向它发来的应答,还是CommandListener::sendBroadcast广播来的UEvent消息,都要进它的onDataAvailable,那怎么判断正收到的是应答,还是广播来?——通过第二个字段,如果第二个字段是数字,表示是命令号(CmdNum),判断为应答,否则是UEvent消息。根据CmdNum,client还可以知道这应答对应的是哪条命令。CmdNum类型是int,但不要有负数,SocketClient初始赋值是0。

一、命令示例:setcfg

setcfg用于向某个网卡设置ip地址。假设要把ip改到192.168.1.116。

1、netd-socket-client生成setcfg命令,并写入netd-socket。

             argv[1]     argv[3]          argv[5]    
66 interface setcfg eth0 192.168.1.116 24 multicast up broadcast running
   argv[0]          argv[2]            argv[4]

66是命令号。eth0指示要改设备中以太网网卡。24是prefixlen,表示子网掩码是255.255.255.0,即高24位是1。

2、(netd内)ComandListener收到、提取出这命令。通过argv[0]是“interface”判断出能处理它的对象是InterfaceCmd,并调用interfaceCmd::runCommand。

3、(netd内)interfaceCmd::runCommand。

int CommandListener::InterfaceCmd::runCommand(SocketClient *cli, int argc, char **argv) {
  ...
  if (!strcmp(argv[1], "setcfg")) {
    ifc_init();
    // 设置网络设备名,新名字保存在ifr_newname中。
    ifc_set_addr(argv[2], 0);
    ifc_add_address(argv[2], argv[3], atoi(argv[4]));
    ifc_up(argv[2]);
    cli->sendMsg(ResponseCode::CommandOkay, "Interface configuration set", false);
    ifc_close();
  }
  ...
}

ifc_xxx是Android为Netdevice编程提供的一些更为简单的API,它们被封装在libnetutils.so中,统称为ifc_utils。使用ifc_utils之前,需调用ifc_init进行初始化,执行指定功能后,须调用ifc_close。

执行ifc_up(argv[2])后,kernel会向netd发来UEvent消息,告知IP发生改变,“二、UEvent示例:IP发生改变”会叙述此个过程。

cli->sendMsg反馈此个命令的应答。ResponseCode::CommandOkay值是200,netd-socket-client将收到下面的应答。

200 66 Interface configuration set

二、UEvent示例:IP发生改变

setcfg执行ifc_up,kernel修改了eth0的IP,于是向netd发来IP地址改变的UEvent,这个UEvent进入NetlinkHandler::onDataAvailable。

bool NetlinkListener::onDataAvailable(SocketClient *cli)
{
  ...
  NetlinkEvent *evt = new NetlinkEvent();
  if (evt->decode(mBuffer, count, mFormat)) {
    onEvent(evt);
  }
  ...
}

UEvent是二进制格式,不易理解。onDataAvailable以这个UEvent为参数,生成对应一个evt,然后调用onEvent。

void NetlinkHandler::onEvent(NetlinkEvent *evt) {
  const char *subsys = evt->getSubsystem();
  if (!subsys) {
    ALOGW("No subsystem found in netlink event");
    return;
  }

  if (!strcmp(subsys, "net")) {
    // 对此时修改到有效IP的IP改变,action值是kAddressUpdated(6)。
    // 如果操作是清除IP时,也是IP改变,action值是kAddressRemoved(7)。
    NetlinkEvent::Action action = evt->getAction();
    const char *iface = evt->findParam("INTERFACE");
    ...
    if (action == NetlinkEvent::Action::kAddressUpdated || action == NetlinkEvent::Action::kAddressRemoved) {
      const char *address = evt->findParam("ADDRESS");
      const char *flags = evt->findParam("FLAGS");
      const char *scope = evt->findParam("SCOPE");
      ...
      if (iface && iface[0] && address && flags && scope) {
        notifyAddressChanged(action, address, iface, flags, scope);
      }
    }
    ...
  }
  ....
}

onEvent会调用notifyAddressChanged处理IP改变消息。

void NetlinkHandler::notifyAddressChanged(NetlinkEvent::Action action, const char *addr,
                                          const char *iface, const char *flags,
                                          const char *scope) {
    notify(ResponseCode::InterfaceAddressChange,
           "Address %s %s %s %s %s",
           (action == NetlinkEvent::Action::kAddressUpdated) ? kUpdated : kRemoved,
           addr, iface, flags, scope);
}

InterfaceAddressChange值是614,notify会生成类似以下的消息,这个消息随即被广播到netd-socket-client。

614 Address updated 192.168.1.116/24 eth0 128 0, false

三、路由、netId

要正确修改出IP,不能单靠一条setcfg,还要改相关路由。以下是一次修改IP可能出现的一连串命令。

21 interface clearaddrs eth0
22 bandwidth gettetherstats
23 bandwidth setglobalalert 2097152
24 network destroy 100
25 interface getcfg eth0
26 interface setcfg eth0 192.168.1.116 24 multicast up broadcast running
27 network create 101
28 network interface add 101 eth0
29 network route add 101 eth0 192.168.1.0/24
30 network route add 101 eth0 0.0.0.0/0 192.168.1.1
31 bandwidth gettetherstats
32 bandwidth gettetherstats
33 network default set 101
34 bandwidth setglobalalert 2097152

“network route add 101 eth0 192.168.1.0/24”用于向eth0这个网卡增加一条路由:192.168.1.0/24。101是netId。执行它后,在命令行执行“ip route”会看到一条destination是“192.168.1.0/24”的路由。

C:\apps-src\apps\projectfiles\android>adb shell su
ip route
192.168.1.0/24 dev eth0  proto kernel  scope link  src 192.168.1.116

表示目的地是192.168.1.0/24这个网段的包,都由网卡eth0发出。src是一个提示,指示eth0网卡当前设置的ip地址是192.168.1.116。

接下看netd是如何处理“route add”命令。“network”的处理对象是NetworkCommand,针对“route add”,NetworkCommand::runCommand会调用NetworkController::addRoute。

int NetworkController::addRoute(unsigned netId, const char* interface, const char* destination,
                                const char* nexthop, bool legacy, uid_t uid) {
    return modifyRoute(netId, interface, destination, nexthop, true, legacy, uid);
}

int NetworkController::modifyRoute(unsigned netId, const char* interface, const char* destination,
                                   const char* nexthop, bool add, bool legacy, uid_t uid)
{
  ...
  // 根据netId+legacy算出tableType。
  RouteController::TableType tableType;
  if (netId == LOCAL_NET_ID) {
    tableType = RouteController::LOCAL_NETWORK;
  } else if (legacy) {
    if ((getPermissionForUser(uid) & PERMISSION_SYSTEM) == PERMISSION_SYSTEM) {
      tableType = RouteController::LEGACY_SYSTEM;
    } else {
      tableType = RouteController::LEGACY_NETWORK;
    }
  } else {
    // netId往往>=100,LOCAL_NET_ID值是99,legacy值一般是false,因而算出的tableType通常进入这里。
    tableType = RouteController::INTERFACE;
  }
  return add ? RouteController::addRoute(interface, destination, nexthop, tableType) :
                 RouteController::removeRoute(interface, destination, nexthop, tableType);
}

int RouteController::addRoute(const char* interface, const char* destination, const char* nexthop,
                              TableType tableType) {
    return modifyRoute(RTM_NEWROUTE, interface, destination, nexthop, tableType);
}

WARN_UNUSED_RESULT int modifyRoute(uint16_t action, const char* interface, const char* destination,
                                   const char* nexthop, RouteController::TableType tableType) {
  // 根据tableType算出talbe。这个table和最终要写入的路由表索引有关。
  uint32_t table;
  switch (tableType) {
    case RouteController::INTERFACE: {
      table = getRouteTableForInterface(interface);
      if (table == RT_TABLE_UNSPEC) {
          return -ESRCH;
      }
      break;
    }
    ...
  }
  int ret = modifyIpRoute(action, table, interface, destination, nexthop);
}

WARN_UNUSED_RESULT int modifyIpRoute(uint16_t action, uint32_t table, const char* interface,
                                     const char* destination, const char* nexthop) {
  ...
  uint16_t flags = (action == RTM_NEWROUTE) ? NETLINK_CREATE_REQUEST_FLAGS :
                                                NETLINK_REQUEST_FLAGS;
  return sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov));
}

sendNetlinkRequest功能是发送netlink请求并处理结果。参数[iov]是包含netlink消息负载的iovec数组。netlink标头(nlmsghdr)由此函数基于[action]和[flags]生成。
int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen) {
  // nlmsghdr指netlink标头。
  nlmsghdr nlmsg = {
    .nlmsg_type = action,
    .nlmsg_flags = flags,
  };
  // iov是caller提供的iovec数组,它的位置0是由此个函数填写。
  iov[0].iov_base = &nlmsg;
  iov[0].iov_len = sizeof(nlmsg);
  // 标头上的nlmsg_len是整个iovec负载中的字节数。
  for (int i = 0; i < iovlen; ++i) {
    nlmsg.nlmsg_len += iov[i].iov_len;
  }

  struct {
    nlmsghdr msg;
    nlmsgerr err;
  } response;

  int sock = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
  connect(sock, reinterpret_cast(&NETLINK_ADDRESS), sizeof(NETLINK_ADDRESS));
  writev(sock, iov, iovlen);
  recv(sock, &response, sizeof(response), 0);
  close(sock);
}

通过看“route add”流程,可发现netId不是最终要写入的路由表索引,只是相关而已,那netdId作用是什么?netId意思是网络标识,int类型,范围[MIN_NET_ID(100), MAX_NET_ID(65535)],或[MIN_OEM_ID(1), MAX_OEM_ID(50)]。它的创建、销毁不涉及到linux kernel。它是netd为方便管理多个网卡(iface)引入的概念,一个iface只能属于某个netId。

network interface add 101 eth0

以上命令希望把eht0分配给netId(101),但如果eth0已经分配给了netId(100),执行将会报错。

interface eth0 already assigned to netId 100。

四、多个netd-socket-client

android通常只有一个netd-socket-client,即NetworkManagementService,如果出现多个会是怎么个情况。举个例子,launcher是android上的远程桌面服务,需要在不弹出界面情况下修改IP,于是新建一个netd-socket-client:NetdListener。此时launcher需注意几个地方。

4.1 如何知道IP地址变了

对app来说,判断之前的IP是否还有效。可用一些网络库提供的方法。像chromium,以要查的ip创建个net::TCPServerSocket,然后ListenWithAddressAndPort绑定并侦听port,成功就认为没变。但这类方法有个问题,如果前、后两次判断时刻IP没变,但之间调用过interface clearaddrs/interface setcfg,它们将错误地判断出这段时间IP“没变”,但结果应该是之间创建的socket必须无效。

解决方法是NetdListener接收UEvent消息,一旦收到“614 Address removed/updated”,须认为IP地址变了。

4.2 修改IP时,尽量不影响NetworkManagementService

目前试下来,NetdListener修改IP后,没法做到不影响NetworkManagementService。一旦NetdListener修改了IP,NetworkManagementService会收到IP变了的UEvent消息,Adnroid 7.1会再用setcfg设回自个保存在配置中的IP。

对这问题,launcher是引入“临时IP地址”。什么是“临时IP地址”?1)该地址只是给后续的远程桌面Client用。2)该地址和“设置/Settings”中的IP地址界面显示的会不一样。3)成功改出临时IP地址后,设备上的IP地址会“混乱”,必须尽快用“设置/Settings”改出永久IP地址。

引入“临时IP地址”,launcher用下面的逻辑修改网卡eth0,改IP为192.168.1.116,子网掩码为255.255.255.0,网关为192.168.1.1。

10 launcher netid eth0
11 launcher broadcast disable
12 interface clearaddrs eth0
13 interface setcfg eth0 192.168.1.116 24 multicast up broadcast running
14 network route add 100 eth0 192.168.1.0/24
15 network route add 100 eth0 0.0.0.0/0 192.168.1.1
16 launcher broadcast enable

10、11、16是新加的命令。第10条是得到eth0在哪个netId,因为设置过程要在eth0当前所在的netId中改。11、16分别是关闭、打开netd中的广播UEvent消息。平时处于打开状态,要开始执行修改了,先关闭。后绪setcfg后,NetworkManagementService不会收到IP变了消息,于是什么也不做,但网卡上的IP的确是被改了。远程桌面Client通过这个临时IP连上设备,必须尽快用“设置/Settings”改出永久IP地址,设备中的IP也就不混乱了。

你可能感兴趣的:(android)