Android Framework Development Guide

Android Netd

简介

Netd是Android的网络守护进程。NetD是个网络管家,封装了复杂的底层各种类型的网络(NAT,PLAN,PPP,SOFTAP,TECHER,ETHO,MDNS等),隔离了底层网络接口的差异,给Framework提供了统一调用接口,简化了网络的使用。NetD主要功能是:

  • 第一、接收Framework的网络请求,处理请求,向Framework层反馈处理结果;
  • 第二、监听网络事件(断开/连接/错误等),向Framework层上报。

NetD程序的大概逻辑: [Kernel] <--Network Event--> (NetD) <--Command--> [Framework]。事件和命令传送采用跨进程通信机制(SOCKET).

Netd:
/System/netd
/system/core/libsysutils/src
/system/core/include/sysutils
Framework:
/frameworks/base/services/java/com/android/server

int main() {

    CommandListener *cl;
    NetlinkManager *nm;
    DnsProxyListener *dpl;
    MDnsSdListener *mdnsl;
    FwmarkServer* fwmarkServer;

    ALOGI("Netd 1.0 starting");

    blockSigpipe();

    //NetlinkManager是用于接收kernel中的uevent
    if (!(nm = NetlinkManager::Instance())) {
        ALOGE("Unable to create NetlinkManager");
        exit(1);
    };

    //接收来自Framework层NetworkManageService的命令
    cl = new CommandListener();
    nm->setBroadcaster((SocketListener *) cl);

    if (nm->start()) {
        ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
        exit(1);
    }

    // Set local DNS mode, to prevent bionic from proxying
    // back to this service, recursively.
    //对接frameworklistener
     //GetAddrInfoCmd,和Bionic C库的getaddrinfo函数对应。
    //GetHostByAddrCmd,和Bionic C库的gethostbyaddr函数对应。

    setenv("ANDROID_DNS_MODE", "local", 1);
    dpl = new DnsProxyListener(CommandListener::sNetCtrl);
    if (dpl->startListener()) {
        ALOGE("Unable to start DnsProxyListener (%s)", strerror(errno));
        exit(1);
    }
    //MDnsSd是Multicast DNS Service Discovery的简称,它和Apple公司的Bonjour技术有关
    //Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。
    //举一个简单的例子:在局域网中,如果要进行打印服务,必须先知道打印服务器的IP地址。
    //此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,
    //打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务?
    //名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。
    //由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机
    mdnsl = new MDnsSdListener();
    if (mdnsl->startListener()) {
        ALOGE("Unable to start MDnsSdListener (%s)", strerror(errno));
        exit(1);
    }

//创建 FwmarkServer并启动监听,它将创建名为fwmarkd的监听
    fwmarkServer = new FwmarkServer(CommandListener::sNetCtrl);
    if (fwmarkServer->startListener()) {
        ALOGE("Unable to start FwmarkServer (%s)", strerror(errno));
        exit(1);
    }

    /*
     * Now that we're up, we can respond to commands
     */
    if (cl->startListener()) {
        ALOGE("Unable to start CommandListener (%s)", strerror(errno));
        exit(1);
    }

    // Eventually we'll become the monitoring thread
    while(1) {
        sleep(1000);
    }

    ALOGI("Netd exiting");
    exit(0);
}

Netd的main函数主要是创建几个重要成员并启动相应的工作,这四个重要成员分别如下: NetlinkManager:接收并处理来自Kernel的UEvent消息。这些消息经NetlinkManager解析后将借助它的Broadcaster(也就是代码中为NetlinkManager设置的CommandListener)发送给Framework层的 NetworkManagementService。

CommandListener、DnsProxyListener、MDnsSDListener、FwmarkServer:分别创建名为netd、dnsproxyd、mdns、fwmarkServer的监听socket.

下面针对上面提到主要成员进行分析。

NetlinkManager分析

int NetlinkManager::start() {
    //创建接收 NETLINK_KOBJECT_UEVENT 消息的socket,其值保存在mUeventSock中
    //其中, NETLINK_FORMAT_ASCII 代表UEvent消息的内容为 ASCII字符串
    if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,
         0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII)) == NULL) {
        return -1;
    }

    //创建接收 RTMGRP_LINK 消息的socket,其值保存在 mRouteSock 中
    //其中, NETLINK_FORMAT_BINARY 代表 UEvent 消息的类型为结构体,故需要进行二进制解析
    if ((mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE,
                                     RTMGRP_LINK |
                                     RTMGRP_IPV4_IFADDR |
                                     RTMGRP_IPV6_IFADDR |
                                     RTMGRP_IPV6_ROUTE |
                                     (1 << (RTNLGRP_ND_USEROPT - 1)),
         NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) {
        return -1;
    }

    //创建接收 NETLINK_NFLOG 消息的socket,其值保存在 mQuotaSock 中
    if ((mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG,
        NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) {
        ALOGE("Unable to open quota2 logging socket");
        // TODO: return -1 once the emulator gets a new kernel.
    }

    return 0;
}

NM的start函数主要是向Kernel注册三个用于接收UEvent事件的socket,这三个UEvent分别对应于以下内容

  • NETLINK_KOBJECT_UEVENT: 代表 kobject 事件,由于这些事件包含的信息由ASCII字符串表述,故上述代码中使用了 NETLINK_FORMAT_ASCII。 它表示将采用字符串解析的方法去解析接收到的UEvent 消息。 kobject 一般用来通知内核中某个模块的加载或卸载。对于NM来说,其关注的是 /sys/class/net下相应模块的加载或卸载消息。
  • NETLINK_ROUTE:代表Kernel中routing或link改变时对应的消息。 NETLINK_ROUTE 包含很多子项,上述代码中使用了 RTMGRP_LINK 项。二者结合起来使用,表示NM希望收到网络链路断开或接通时对应的UEvent消息。由于对应UEvent消息内部封装了 nlmsghdr 等相关结构体,故上述代码使用了
  • NETLINK_FORMAT_BINARY来指示解析 UEvent消息时将使用二进制的解析方法。
  • NETLINK_NFLOG:和带宽控制有关。Netd中的带宽控制可以设置一个预警值,当网络数据超过一定字节数就会触发Kernel发送一个警告。该功能属于iptables 的扩展项。值得指出的是,上述代码中有关 NETLINK_NFLOG 相关socket的设置并非所有 Kernel版本都支持。同时,
  • NFLOG_QUOTA_GROUP 的值是直接定义在 NetlinkManager.cpp中的,而非和其他类似系统定义一样定义在系统头文件中,这也表明 NFLOG_QUOTA_GROUP 的功能比较新。

NetlinkHandler接收到的UEvent消息会转换成一个NetlinkEvent对象。NetlinkEvent对象封装了对 UEvent消息的解析方法。对于 NETLINK_FORMAT_ASCII类型,其 parseAsciiNetlinkMessage函数会被调用,而对于 NETLINK_FORMAT_BINARY类型,其 parseBinaryNetlinkMessage 函数会被调用。 NM处理流程的输入为一个解析后的 NetlinkEvent对象。NM完成相应工作后,其处理结果将经由 mBroadcaster对象传递给 Framework层的接收者,也就是 NetworkManagementService。 CommandListener 从 FrameworkListener派生,而 FrameworkListener 内部有一个数组 mCommands,用来存储注册到 FrameworkListener中的命令处理对象。

CommandListener分析

Netd中第二个重要成员是CommandListener(以后简称CL),其主要作用是接收来自Framework层NetworkManageService的命令。从角色来看,CL仅是一个Listener。它在收到命令后,只是将它们转交给对应的命令处理对象去处理。CL内部定义了许多命令,而这些命令都有较深的背景知识。本节拟以分析CL的工作流程为主,而相关的命令处理则放到后文再集中分析。

CL定义了11个和网络相关的Command类。这些类均从NetdCommand派生(注意,为保持绘图简洁,这11个Command的派生关系由1个派生箭头表达)。 CL还定义了10个控制类,这些控制类将和命令类共同完成相应的命令处理工作


android-5.1/system/netd/server/CommandListener.cpp

CommandListener::CommandListener() :
                 FrameworkListener("netd", true) {
    registerCmd(new InterfaceCmd());//注册11个命令类对象
    registerCmd(new IpFwdCmd());
    registerCmd(new TetherCmd());
    registerCmd(new NatCmd());
    registerCmd(new ListTtysCmd());
    registerCmd(new PppdCmd());
    registerCmd(new SoftapCmd());
    registerCmd(new BandwidthControlCmd());
    registerCmd(new IdletimerControlCmd());
    registerCmd(new ResolverCmd());
    registerCmd(new FirewallCmd());
    registerCmd(new ClatdCmd());
    registerCmd(new NetworkCommand());

    //创建对应的控制类对象
    if (!sNetCtrl)
        sNetCtrl = new NetworkController();
    if (!sTetherCtrl)
        sTetherCtrl = new TetherController();
    if (!sNatCtrl)
        sNatCtrl = new NatController();
    if (!sPppCtrl)
        sPppCtrl = new PppController();
    if (!sSoftapCtrl)
        sSoftapCtrl = new SoftapController();
    if (!sBandwidthCtrl)
        sBandwidthCtrl = new BandwidthController();
    if (!sIdletimerCtrl)
        sIdletimerCtrl = new IdletimerController();
    if (!sResolverCtrl)
        sResolverCtrl = new ResolverController();
    if (!sFirewallCtrl)
        sFirewallCtrl = new FirewallController();
    if (!sInterfaceCtrl)
        sInterfaceCtrl = new InterfaceController();
    if (!sClatdCtrl)
        sClatdCtrl = new ClatdController(sNetCtrl);

    /*
     * This is the only time we touch top-level chains in iptables; controllers
     * should only mutate rules inside of their children chains, as created by
     * the constants above.
     *
     * Modules should never ACCEPT packets (except in well-justified cases);
     * they should instead defer to any remaining modules using RETURN, or
     * otherwise DROP/REJECT.
     */

    // Create chains for children modules
    //初始化 iptables 中的各个Table 及相应 Chain 和 Rules
    createChildChains(V4V6, "filter", "INPUT", FILTER_INPUT);
    createChildChains(V4V6, "filter", "FORWARD", FILTER_FORWARD);
    createChildChains(V4V6, "filter", "OUTPUT", FILTER_OUTPUT);
    createChildChains(V4V6, "raw", "PREROUTING", RAW_PREROUTING);
    createChildChains(V4V6, "mangle", "POSTROUTING", MANGLE_POSTROUTING);
    createChildChains(V4, "mangle", "FORWARD", MANGLE_FORWARD);
    createChildChains(V4, "nat", "PREROUTING", NAT_PREROUTING);
    createChildChains(V4, "nat", "POSTROUTING", NAT_POSTROUTING);

    // Let each module setup their child chains
    //netd允许OEM厂商可自定义一些规则,这些规则在 /system/bin/oem-iptables-init.sh文件中保存
    setupOemIptablesHook();

    /* When enabled, DROPs all packets except those matching rules. */
    //初始化 iptables中的一些chain,以及初始化路由表
    sFirewallCtrl->setupIptablesHooks();

    /* Does DROPs in FORWARD by default */
    sNatCtrl->setupIptablesHooks();
    /*
     * Does REJECT in INPUT, OUTPUT. Does counting also.
     * No DROP/REJECT allowed later in netfilter-flow hook order.
     */
    sBandwidthCtrl->setupIptablesHooks();
    /*
     * Counts in nat: PREROUTING, POSTROUTING.
     * No DROP/REJECT allowed later in netfilter-flow hook order.
     */
    sIdletimerCtrl->setupIptablesHooks();

    //初始化时,Netd将禁止带宽控制功能
    sBandwidthCtrl->enableBandwidthControl(false);

    if (int ret = RouteController::Init(NetworkController::LOCAL_NET_ID)) {
        ALOGE("failed to initialize RouteController (%s)", strerror(-ret));
    }
}

DnsProxyListener分析

DNS是Domain Name System(域名系统)的缩写。其主要目的是在域名和IP地址之间建立一种映射。简单点说,DNS的功能类似于电话簿,它可将人名映射到相应的电话号码。在DNS中,人名就是域名,电话号码就是IP地址。域名系统的管理由DNS服务器来完成。全球范围内的DNS服务器共同构成了一个分布式的域名-IP数据库。 对使用域名来发起网络操作的网络程序来说,其域名解析工作主要分两步: 1)第一步工作就是需要将域名转换成IP。由于域名和IP的转换关系存储在DNS服务器上,所以该网络程序要向DNS服务器发起请求,以获取域名对应的IP地址。 2)DNS服务器根据DNS解析规则解析并得到该域名对应的IP地址,然后返回给客户端。在DNS中,每一个域名和IP的对应关系被称之为一条记录。客户端一般会缓存这条记录以备后续之用。 提醒:DNS解析规则比较复杂,感兴趣的读者可研究DNS的相关协议。

对软件开发者来说,常用的域名解析socket API有两个:

  • getaddrinfo:它根据指定的host名或service名得到对应的IP地址(该IP地址由结构体addrinfo表达)。
  • getnameinfo:根据指定的IP地址(由结构体sockaddr表达)得到对应的host或service的名称。
android-5.1/bionic/libc/dns/net/getaddrinfo.c

int
getaddrinfo(const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res)
{
    //Android平台的特殊定制
    return android_getaddrinfofornet(hostname, servname, hints, NETID_UNSET, MARK_UNSET, res);
}
Android平台的 getaddrinfo 会调用其定制的 android_getaddrinfofornet 函数完成一些特殊操作:
int
android_getaddrinfofornet(const char *hostname, const char *servname,
    const struct addrinfo *hints, unsigned netid, unsigned mark, struct addrinfo **res)
{
    struct addrinfo sentinel;
    struct addrinfo *cur;
    int error = 0;
    struct addrinfo ai;
    struct addrinfo ai0;
    struct addrinfo *pai;
    const struct explore *ex;
    //取 ANDROID_DNS_MODE 环境变量
    //只有android-5.1/system/netd/server/main.cpp中设置了此变量:setenv("ANDROID_DNS_MODE", "local", 1);
    const char* cache_mode = getenv("ANDROID_DNS_MODE");

    /* hostname is allowed to be NULL */
    /* servname is allowed to be NULL */
    /* hints is allowed to be NULL */
    assert(res != NULL);
    memset(&sentinel, 0, sizeof(sentinel));
    cur = &sentinel;
    pai = &ai;
    pai->ai_flags = 0;
    pai->ai_family = PF_UNSPEC;
    pai->ai_socktype = ANY;
    pai->ai_protocol = ANY;
    pai->ai_addrlen = 0;
    pai->ai_canonname = NULL;
    pai->ai_addr = NULL;
    pai->ai_next = NULL;

    if (hostname == NULL && servname == NULL)
        return EAI_NONAME;
    if (hints) {
        /* error check for hints */
        if (hints->ai_addrlen || hints->ai_canonname ||
            hints->ai_addr || hints->ai_next)
            ERR(EAI_BADHINTS); /* xxx */
        if (hints->ai_flags & ~AI_MASK)
            ERR(EAI_BADFLAGS);
        switch (hints->ai_family) {
        case PF_UNSPEC:
        case PF_INET:
#ifdef INET6
        case PF_INET6:
#endif
            break;
        default:
            ERR(EAI_FAMILY);
        }
        memcpy(pai, hints, sizeof(*pai));

        /*
         * if both socktype/protocol are specified, check if they
         * are meaningful combination.
         */
        if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
            for (ex = explore; ex->e_af >= 0; ex++) {
                if (pai->ai_family != ex->e_af)
                    continue;
                if (ex->e_socktype == ANY)
                    continue;
                if (ex->e_protocol == ANY)
                    continue;
                if (pai->ai_socktype == ex->e_socktype
                 && pai->ai_protocol != ex->e_protocol) {
                    ERR(EAI_BADHINTS);
                }
            }
        }
    }

    /*
     * check for special cases.  (1) numeric servname is disallowed if
     * socktype/protocol are left unspecified. (2) servname is disallowed
     * for raw and other inet{,6} sockets.
     */
    if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
#ifdef PF_INET6
     || MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
#endif
        ) {
        ai0 = *pai; /* backup *pai */

        if (pai->ai_family == PF_UNSPEC) {
#ifdef PF_INET6
            pai->ai_family = PF_INET6;
#else
            pai->ai_family = PF_INET;
#endif
        }
        error = get_portmatch(pai, servname);
        if (error)
            ERR(error);

        *pai = ai0;
    }

    ai0 = *pai;

    /* NULL hostname, or numeric hostname */
    for (ex = explore; ex->e_af >= 0; ex++) {
        *pai = ai0;

        /* PF_UNSPEC entries are prepared for DNS queries only */
        if (ex->e_af == PF_UNSPEC)
            continue;

        if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex)))
            continue;
        if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex)))
            continue;
        if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex)))
            continue;

        if (pai->ai_family == PF_UNSPEC)
            pai->ai_family = ex->e_af;
        if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
            pai->ai_socktype = ex->e_socktype;
        if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
            pai->ai_protocol = ex->e_protocol;

        if (hostname == NULL)
            error = explore_null(pai, servname, &cur->ai_next);
        else
            error = explore_numeric_scope(pai, hostname, servname,
                &cur->ai_next);

        if (error)
            goto free;

        while (cur->ai_next)
            cur = cur->ai_next;
    }

    /*
     * XXX
     * If numeric representation of AF1 can be interpreted as FQDN
     * representation of AF2, we need to think again about the code below.
     */
    if (sentinel.ai_next)
        goto good;

    if (hostname == NULL)
        ERR(EAI_NODATA);
    if (pai->ai_flags & AI_NUMERICHOST)
        ERR(EAI_NONAME);

        /*
         * BEGIN ANDROID CHANGES; proxying to the cache
         */
    //如果是Netd进程调用,则判断不成立,因为两个都返回0
    //如果非Netd进程调用,则判断成立
    if (cache_mode == NULL || strcmp(cache_mode, "local") != 0) {
        // we're not the proxy - pass the request to them
        return android_getaddrinfo_proxy(hostname, servname, hints, res, netid);
    }

    /*
     * hostname as alphabetical name.
     * we would like to prefer AF_INET6 than AF_INET, so we'll make a
     * outer loop by AFs.
     */
    for (ex = explore; ex->e_af >= 0; ex++) {
        *pai = ai0;

        /* require exact match for family field */
        if (pai->ai_family != ex->e_af)
            continue;

        if (!MATCH(pai->ai_socktype, ex->e_socktype,
                WILD_SOCKTYPE(ex))) {
            continue;
        }
        if (!MATCH(pai->ai_protocol, ex->e_protocol,
                WILD_PROTOCOL(ex))) {
            continue;
        }

        if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
            pai->ai_socktype = ex->e_socktype;
        if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
            pai->ai_protocol = ex->e_protocol;

        error = explore_fqdn(pai, hostname, servname,
            &cur->ai_next, netid, mark);

        while (cur && cur->ai_next)
            cur = cur->ai_next;
    }

    /* XXX */
    if (sentinel.ai_next)
        error = 0;

    if (error)
        goto free;
    if (error == 0) {
        if (sentinel.ai_next) {
 good:
            *res = sentinel.ai_next;
            return SUCCESS;
        } else
            error = EAI_FAIL;
    }
 free:
 bad:
    if (sentinel.ai_next)
        freeaddrinfo(sentinel.ai_next);
    *res = NULL;
    return error;
}

非Netd进程调用时,会执行函数 android_getaddrinfo_proxy。


static int
android_getaddrinfo_proxy(
    const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res, unsigned netid)
{
    int sock;
    const int one = 1;
    struct sockaddr_un proxy_addr;
    FILE* proxy = NULL;
    int success = 0;

    // Clear this at start, as we use its non-NULLness later (in the
    // error path) to decide if we have to free up any memory we
    // allocated in the process (before failing).
    *res = NULL;

    // Bogus things we can't serialize.  Don't use the proxy.  These will fail - let them.
    if ((hostname != NULL &&
         strcspn(hostname, " \n\r\t^'\"") != strlen(hostname)) ||
        (servname != NULL &&
         strcspn(servname, " \n\r\t^'\"") != strlen(servname))) {
        return EAI_NODATA;
    }

    //建立和Netd中 DnsProxyListener 的连接,将请求转发给它去执行
    sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
    if (sock < 0) {
        return EAI_NODATA;
    }

    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    memset(&proxy_addr, 0, sizeof(proxy_addr));
    proxy_addr.sun_family = AF_UNIX;
    strlcpy(proxy_addr.sun_path, "/dev/socket/dnsproxyd",
        sizeof(proxy_addr.sun_path));
    if (TEMP_FAILURE_RETRY(connect(sock,
                       (const struct sockaddr*) &proxy_addr,
                       sizeof(proxy_addr))) != 0) {
        close(sock);
        return EAI_NODATA;
    }

    netid = __netdClientDispatch.netIdForResolv(netid);

    // Send the request.
    //发送请求,处理回复等
    proxy = fdopen(sock, "r+");
    if (fprintf(proxy, "getaddrinfo %s %s %d %d %d %d %u",
            hostname == NULL ? "^" : hostname,
            servname == NULL ? "^" : servname,
            hints == NULL ? -1 : hints->ai_flags,
            hints == NULL ? -1 : hints->ai_family,
            hints == NULL ? -1 : hints->ai_socktype,
            hints == NULL ? -1 : hints->ai_protocol,
            netid) < 0) {
        goto exit;
    }
    // literal NULL byte at end, required by FrameworkListener
    if (fputc(0, proxy) == EOF ||
        fflush(proxy) != 0) {
        goto exit;
    }

    char buf[4];
    // read result code for gethostbyaddr
    if (fread(buf, 1, sizeof(buf), proxy) != sizeof(buf)) {
        goto exit;
    }

    int result_code = (int)strtol(buf, NULL, 10);
    // verify the code itself
    if (result_code != DnsProxyQueryResult ) {
        fread(buf, 1, sizeof(buf), proxy);
        goto exit;
    }

    struct addrinfo* ai = NULL;
    struct addrinfo** nextres = res;
    while (1) {
        uint32_t addrinfo_len;
        if (fread(&addrinfo_len, sizeof(addrinfo_len),
              1, proxy) != 1) {
            break;
        }
        addrinfo_len = ntohl(addrinfo_len);
        if (addrinfo_len == 0) {
            success = 1;
            break;
        }

        if (addrinfo_len < sizeof(struct addrinfo)) {
            break;
        }
        struct addrinfo* ai = calloc(1, addrinfo_len +
                         sizeof(struct sockaddr_storage));
        if (ai == NULL) {
            break;
        }

        if (fread(ai, addrinfo_len, 1, proxy) != 1) {
            // Error; fall through.
            break;
        }

        // Zero out the pointer fields we copied which aren't
        // valid in this address space.
        ai->ai_addr = NULL;
        ai->ai_canonname = NULL;
        ai->ai_next = NULL;

        // struct sockaddr
        uint32_t addr_len;
        if (fread(&addr_len, sizeof(addr_len), 1, proxy) != 1) {
            break;
        }
        addr_len = ntohl(addr_len);
        if (addr_len != 0) {
            if (addr_len > sizeof(struct sockaddr_storage)) {
                // Bogus; too big.
                break;
            }
            struct sockaddr* addr = (struct sockaddr*)(ai + 1);
            if (fread(addr, addr_len, 1, proxy) != 1) {
                break;
            }
            ai->ai_addr = addr;
        }

        // cannonname
        uint32_t name_len;
        if (fread(&name_len, sizeof(name_len), 1, proxy) != 1) {
            break;
        }
        name_len = ntohl(name_len);
        if (name_len != 0) {
            ai->ai_canonname = (char*) malloc(name_len);
            if (fread(ai->ai_canonname, name_len, 1, proxy) != 1) {
                break;
            }
            if (ai->ai_canonname[name_len - 1] != '\0') {
                // The proxy should be returning this
                // NULL-terminated.
                break;
            }
        }

        *nextres = ai;
        nextres = &ai->ai_next;
        ai = NULL;
    }

    if (ai != NULL) {
        // Clean up partially-built addrinfo that we never ended up
        // attaching to the response.
        freeaddrinfo(ai);
    }
exit:
    if (proxy != NULL) {
        fclose(proxy);
    }

    if (success) {
        return 0;
    }

    // Proxy failed;
    // clean up memory we might've allocated.
    if (*res) {
        freeaddrinfo(*res);
        *res = NULL;
    }
    return EAI_NODATA;
}

当非Netd进程调用 getaddrinfo 时,会调用函数 android_getaddrinfo_proxy, 它将和Netd进程中的 dnsproxyd 监听 socket建立连接,然后把请求发给 DNSProxyListener去执行。当然最后还是会调回到 getaddrinfo 去执行,而此时就变成了 Netd进程的调用了,Netd进程将向指定的 DNS 服务器发起请求以解析域名。 Android系统中通过这种方式来管理DNS的好处是,所有解析后得到的 DNS 记录都将缓存在 Netd 进程中,从而使这些信息成为一个公共的资源,最大程度做到信息共享。

MDnsSdListener分析

MDnsSd是Multicast DNS Service Discovery的简称,它和Apple公司的Bonjour技术有关,故本节将先介绍Apple Bonjour技术。 Bonjour是法语中的Hello之意。它是Apple公司为基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。使用Bonjour的设备在网络中自动组播它们自己的服务信息并监听其他设备的服务信息,设备之间就像在打招呼,这也是该技术命名为Bonjour的原因。Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。 举一个简单的例子:在局域网中,如果要进行打印服务,就必须先知道打印服务器的IP地址。此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务,名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机。在Bonjour的帮助下,客户端最终能找到这台注册了“print service”名字的打印机,并获得它的IP地址以及端口号。 从Bonjour角度来看,该技术主要解决了三个问题:

  • Addressing:即为主机分配IP。Bonjour的Addressing处理比较简单,即每个主机在网络内部的地址可选范围内找一个IP,然后查看下网络内部是否有其他主机再用。如果该IP没有被分配的话,它将使用此IP。
  • Naming:Naming解决的就是host和IP地址的对应关系。Bonjour采用的是Multiple DNS技术,即DNS查询消息将通过UDP组播方式发送。一旦网络内部某个机器发现查询的机器名和自己设置的一样,就回复这条请求。此外,Bonjour还拓展了MDNS的用途,即除了能查找host外,还支持对service的查找。不过,Bonjour的Naming有一个限制,即网络内部不能有重名的host或service。
  • Service Discovery:SD基于上面的Naming工作,它使得应用程序能查找到网络内部的服务,并解析该服务对应的IP地址和端口号。应用程序一旦得到服务的IP地址和端口号,就可以直接和该服务建立交互关系。

MDnsSdListener 对应的 Framework 层服务为 NsdService(Nsd为 Network Service Discovery的缩写)。 MDnsSdListener的内部类 Monitor 用于和 mdnsd 后台进程通信,它将调用Bonjour AIP。 Monitor 内部针对每个 DNSService 都会建立一个 Element 对象,该对象通过 Monitor 的 mHead 指针保存在一个list中。 MDnsSdListener 运行过程主要可分成三步:

  • 1.Netd创建 MDnsSdListener对象,其内部会创建 Monitor对象,而Monitor对象将启动一个线程用于和 mdnsd通信,并接收来自Handler的请求。
  • 2.NsdService 启动完毕后将向 MDnsSdListener发送 "start-service"命令。
  • 3.NsdService 响应应用程序的请求,向 MDnsSdListener 发送其他命令,例如 "discovery" 等。Monitor将最终处理这些请求。

总结

Netd的处理流程:

  • 1、NM接收 Kernel 的UEvent消息,然后转发给 Framework 层的客户端
  • 2、CL、DPL以及 MDnsSdListener 接收来自客户端的请求并处理它们。

引用

Netd工作流程

Android服务之网络服务发现服务解析

Android Says Bonjour

Network Daemon(Android Netd)架构和源码分析

Android Netd

简介

Netd是Android的网络守护进程。NetD是个网络管家,封装了复杂的底层各种类型的网络(NAT,PLAN,PPP,SOFTAP,TECHER,ETHO,MDNS等),隔离了底层网络接口的差异,给Framework提供了统一调用接口,简化了网络的使用。NetD主要功能是:

  • 第一、接收Framework的网络请求,处理请求,向Framework层反馈处理结果;
  • 第二、监听网络事件(断开/连接/错误等),向Framework层上报。

NetD程序的大概逻辑: [Kernel] <--Network Event--> (NetD) <--Command--> [Framework]。事件和命令传送采用跨进程通信机制(SOCKET).

Netd:
/System/netd
/system/core/libsysutils/src
/system/core/include/sysutils
Framework:
/frameworks/base/services/java/com/android/server

int main() {

    CommandListener *cl;
    NetlinkManager *nm;
    DnsProxyListener *dpl;
    MDnsSdListener *mdnsl;
    FwmarkServer* fwmarkServer;

    ALOGI("Netd 1.0 starting");

    blockSigpipe();

    //NetlinkManager是用于接收kernel中的uevent
    if (!(nm = NetlinkManager::Instance())) {
        ALOGE("Unable to create NetlinkManager");
        exit(1);
    };

    //接收来自Framework层NetworkManageService的命令
    cl = new CommandListener();
    nm->setBroadcaster((SocketListener *) cl);

    if (nm->start()) {
        ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
        exit(1);
    }

    // Set local DNS mode, to prevent bionic from proxying
    // back to this service, recursively.
    //对接frameworklistener
     //GetAddrInfoCmd,和Bionic C库的getaddrinfo函数对应。
    //GetHostByAddrCmd,和Bionic C库的gethostbyaddr函数对应。

    setenv("ANDROID_DNS_MODE", "local", 1);
    dpl = new DnsProxyListener(CommandListener::sNetCtrl);
    if (dpl->startListener()) {
        ALOGE("Unable to start DnsProxyListener (%s)", strerror(errno));
        exit(1);
    }
    //MDnsSd是Multicast DNS Service Discovery的简称,它和Apple公司的Bonjour技术有关
    //Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。
    //举一个简单的例子:在局域网中,如果要进行打印服务,必须先知道打印服务器的IP地址。
    //此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,
    //打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务?
    //名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。
    //由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机
    mdnsl = new MDnsSdListener();
    if (mdnsl->startListener()) {
        ALOGE("Unable to start MDnsSdListener (%s)", strerror(errno));
        exit(1);
    }

//创建 FwmarkServer并启动监听,它将创建名为fwmarkd的监听
    fwmarkServer = new FwmarkServer(CommandListener::sNetCtrl);
    if (fwmarkServer->startListener()) {
        ALOGE("Unable to start FwmarkServer (%s)", strerror(errno));
        exit(1);
    }

    /*
     * Now that we're up, we can respond to commands
     */
    if (cl->startListener()) {
        ALOGE("Unable to start CommandListener (%s)", strerror(errno));
        exit(1);
    }

    // Eventually we'll become the monitoring thread
    while(1) {
        sleep(1000);
    }

    ALOGI("Netd exiting");
    exit(0);
}

Netd的main函数主要是创建几个重要成员并启动相应的工作,这四个重要成员分别如下: NetlinkManager:接收并处理来自Kernel的UEvent消息。这些消息经NetlinkManager解析后将借助它的Broadcaster(也就是代码中为NetlinkManager设置的CommandListener)发送给Framework层的 NetworkManagementService。

CommandListener、DnsProxyListener、MDnsSDListener、FwmarkServer:分别创建名为netd、dnsproxyd、mdns、fwmarkServer的监听socket.

下面针对上面提到主要成员进行分析。

NetlinkManager分析

int NetlinkManager::start() {
    //创建接收 NETLINK_KOBJECT_UEVENT 消息的socket,其值保存在mUeventSock中
    //其中, NETLINK_FORMAT_ASCII 代表UEvent消息的内容为 ASCII字符串
    if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,
         0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII)) == NULL) {
        return -1;
    }

    //创建接收 RTMGRP_LINK 消息的socket,其值保存在 mRouteSock 中
    //其中, NETLINK_FORMAT_BINARY 代表 UEvent 消息的类型为结构体,故需要进行二进制解析
    if ((mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE,
                                     RTMGRP_LINK |
                                     RTMGRP_IPV4_IFADDR |
                                     RTMGRP_IPV6_IFADDR |
                                     RTMGRP_IPV6_ROUTE |
                                     (1 << (RTNLGRP_ND_USEROPT - 1)),
         NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) {
        return -1;
    }

    //创建接收 NETLINK_NFLOG 消息的socket,其值保存在 mQuotaSock 中
    if ((mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG,
        NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) {
        ALOGE("Unable to open quota2 logging socket");
        // TODO: return -1 once the emulator gets a new kernel.
    }

    return 0;
}

NM的start函数主要是向Kernel注册三个用于接收UEvent事件的socket,这三个UEvent分别对应于以下内容

  • NETLINK_KOBJECT_UEVENT: 代表 kobject 事件,由于这些事件包含的信息由ASCII字符串表述,故上述代码中使用了 NETLINK_FORMAT_ASCII。 它表示将采用字符串解析的方法去解析接收到的UEvent 消息。 kobject 一般用来通知内核中某个模块的加载或卸载。对于NM来说,其关注的是 /sys/class/net下相应模块的加载或卸载消息。
  • NETLINK_ROUTE:代表Kernel中routing或link改变时对应的消息。 NETLINK_ROUTE 包含很多子项,上述代码中使用了 RTMGRP_LINK 项。二者结合起来使用,表示NM希望收到网络链路断开或接通时对应的UEvent消息。由于对应UEvent消息内部封装了 nlmsghdr 等相关结构体,故上述代码使用了
  • NETLINK_FORMAT_BINARY来指示解析 UEvent消息时将使用二进制的解析方法。
  • NETLINK_NFLOG:和带宽控制有关。Netd中的带宽控制可以设置一个预警值,当网络数据超过一定字节数就会触发Kernel发送一个警告。该功能属于iptables 的扩展项。值得指出的是,上述代码中有关 NETLINK_NFLOG 相关socket的设置并非所有 Kernel版本都支持。同时,
  • NFLOG_QUOTA_GROUP 的值是直接定义在 NetlinkManager.cpp中的,而非和其他类似系统定义一样定义在系统头文件中,这也表明 NFLOG_QUOTA_GROUP 的功能比较新。

NetlinkHandler接收到的UEvent消息会转换成一个NetlinkEvent对象。NetlinkEvent对象封装了对 UEvent消息的解析方法。对于 NETLINK_FORMAT_ASCII类型,其 parseAsciiNetlinkMessage函数会被调用,而对于 NETLINK_FORMAT_BINARY类型,其 parseBinaryNetlinkMessage 函数会被调用。 NM处理流程的输入为一个解析后的 NetlinkEvent对象。NM完成相应工作后,其处理结果将经由 mBroadcaster对象传递给 Framework层的接收者,也就是 NetworkManagementService。 CommandListener 从 FrameworkListener派生,而 FrameworkListener 内部有一个数组 mCommands,用来存储注册到 FrameworkListener中的命令处理对象。

CommandListener分析

Netd中第二个重要成员是CommandListener(以后简称CL),其主要作用是接收来自Framework层NetworkManageService的命令。从角色来看,CL仅是一个Listener。它在收到命令后,只是将它们转交给对应的命令处理对象去处理。CL内部定义了许多命令,而这些命令都有较深的背景知识。本节拟以分析CL的工作流程为主,而相关的命令处理则放到后文再集中分析。

CL定义了11个和网络相关的Command类。这些类均从NetdCommand派生(注意,为保持绘图简洁,这11个Command的派生关系由1个派生箭头表达)。 CL还定义了10个控制类,这些控制类将和命令类共同完成相应的命令处理工作


android-5.1/system/netd/server/CommandListener.cpp

CommandListener::CommandListener() :
                 FrameworkListener("netd", true) {
    registerCmd(new InterfaceCmd());//注册11个命令类对象
    registerCmd(new IpFwdCmd());
    registerCmd(new TetherCmd());
    registerCmd(new NatCmd());
    registerCmd(new ListTtysCmd());
    registerCmd(new PppdCmd());
    registerCmd(new SoftapCmd());
    registerCmd(new BandwidthControlCmd());
    registerCmd(new IdletimerControlCmd());
    registerCmd(new ResolverCmd());
    registerCmd(new FirewallCmd());
    registerCmd(new ClatdCmd());
    registerCmd(new NetworkCommand());

    //创建对应的控制类对象
    if (!sNetCtrl)
        sNetCtrl = new NetworkController();
    if (!sTetherCtrl)
        sTetherCtrl = new TetherController();
    if (!sNatCtrl)
        sNatCtrl = new NatController();
    if (!sPppCtrl)
        sPppCtrl = new PppController();
    if (!sSoftapCtrl)
        sSoftapCtrl = new SoftapController();
    if (!sBandwidthCtrl)
        sBandwidthCtrl = new BandwidthController();
    if (!sIdletimerCtrl)
        sIdletimerCtrl = new IdletimerController();
    if (!sResolverCtrl)
        sResolverCtrl = new ResolverController();
    if (!sFirewallCtrl)
        sFirewallCtrl = new FirewallController();
    if (!sInterfaceCtrl)
        sInterfaceCtrl = new InterfaceController();
    if (!sClatdCtrl)
        sClatdCtrl = new ClatdController(sNetCtrl);

    /*
     * This is the only time we touch top-level chains in iptables; controllers
     * should only mutate rules inside of their children chains, as created by
     * the constants above.
     *
     * Modules should never ACCEPT packets (except in well-justified cases);
     * they should instead defer to any remaining modules using RETURN, or
     * otherwise DROP/REJECT.
     */

    // Create chains for children modules
    //初始化 iptables 中的各个Table 及相应 Chain 和 Rules
    createChildChains(V4V6, "filter", "INPUT", FILTER_INPUT);
    createChildChains(V4V6, "filter", "FORWARD", FILTER_FORWARD);
    createChildChains(V4V6, "filter", "OUTPUT", FILTER_OUTPUT);
    createChildChains(V4V6, "raw", "PREROUTING", RAW_PREROUTING);
    createChildChains(V4V6, "mangle", "POSTROUTING", MANGLE_POSTROUTING);
    createChildChains(V4, "mangle", "FORWARD", MANGLE_FORWARD);
    createChildChains(V4, "nat", "PREROUTING", NAT_PREROUTING);
    createChildChains(V4, "nat", "POSTROUTING", NAT_POSTROUTING);

    // Let each module setup their child chains
    //netd允许OEM厂商可自定义一些规则,这些规则在 /system/bin/oem-iptables-init.sh文件中保存
    setupOemIptablesHook();

    /* When enabled, DROPs all packets except those matching rules. */
    //初始化 iptables中的一些chain,以及初始化路由表
    sFirewallCtrl->setupIptablesHooks();

    /* Does DROPs in FORWARD by default */
    sNatCtrl->setupIptablesHooks();
    /*
     * Does REJECT in INPUT, OUTPUT. Does counting also.
     * No DROP/REJECT allowed later in netfilter-flow hook order.
     */
    sBandwidthCtrl->setupIptablesHooks();
    /*
     * Counts in nat: PREROUTING, POSTROUTING.
     * No DROP/REJECT allowed later in netfilter-flow hook order.
     */
    sIdletimerCtrl->setupIptablesHooks();

    //初始化时,Netd将禁止带宽控制功能
    sBandwidthCtrl->enableBandwidthControl(false);

    if (int ret = RouteController::Init(NetworkController::LOCAL_NET_ID)) {
        ALOGE("failed to initialize RouteController (%s)", strerror(-ret));
    }
}

DnsProxyListener分析

DNS是Domain Name System(域名系统)的缩写。其主要目的是在域名和IP地址之间建立一种映射。简单点说,DNS的功能类似于电话簿,它可将人名映射到相应的电话号码。在DNS中,人名就是域名,电话号码就是IP地址。域名系统的管理由DNS服务器来完成。全球范围内的DNS服务器共同构成了一个分布式的域名-IP数据库。 对使用域名来发起网络操作的网络程序来说,其域名解析工作主要分两步: 1)第一步工作就是需要将域名转换成IP。由于域名和IP的转换关系存储在DNS服务器上,所以该网络程序要向DNS服务器发起请求,以获取域名对应的IP地址。 2)DNS服务器根据DNS解析规则解析并得到该域名对应的IP地址,然后返回给客户端。在DNS中,每一个域名和IP的对应关系被称之为一条记录。客户端一般会缓存这条记录以备后续之用。 提醒:DNS解析规则比较复杂,感兴趣的读者可研究DNS的相关协议。

对软件开发者来说,常用的域名解析socket API有两个:

  • getaddrinfo:它根据指定的host名或service名得到对应的IP地址(该IP地址由结构体addrinfo表达)。
  • getnameinfo:根据指定的IP地址(由结构体sockaddr表达)得到对应的host或service的名称。
android-5.1/bionic/libc/dns/net/getaddrinfo.c

int
getaddrinfo(const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res)
{
    //Android平台的特殊定制
    return android_getaddrinfofornet(hostname, servname, hints, NETID_UNSET, MARK_UNSET, res);
}
Android平台的 getaddrinfo 会调用其定制的 android_getaddrinfofornet 函数完成一些特殊操作:
int
android_getaddrinfofornet(const char *hostname, const char *servname,
    const struct addrinfo *hints, unsigned netid, unsigned mark, struct addrinfo **res)
{
    struct addrinfo sentinel;
    struct addrinfo *cur;
    int error = 0;
    struct addrinfo ai;
    struct addrinfo ai0;
    struct addrinfo *pai;
    const struct explore *ex;
    //取 ANDROID_DNS_MODE 环境变量
    //只有android-5.1/system/netd/server/main.cpp中设置了此变量:setenv("ANDROID_DNS_MODE", "local", 1);
    const char* cache_mode = getenv("ANDROID_DNS_MODE");

    /* hostname is allowed to be NULL */
    /* servname is allowed to be NULL */
    /* hints is allowed to be NULL */
    assert(res != NULL);
    memset(&sentinel, 0, sizeof(sentinel));
    cur = &sentinel;
    pai = &ai;
    pai->ai_flags = 0;
    pai->ai_family = PF_UNSPEC;
    pai->ai_socktype = ANY;
    pai->ai_protocol = ANY;
    pai->ai_addrlen = 0;
    pai->ai_canonname = NULL;
    pai->ai_addr = NULL;
    pai->ai_next = NULL;

    if (hostname == NULL && servname == NULL)
        return EAI_NONAME;
    if (hints) {
        /* error check for hints */
        if (hints->ai_addrlen || hints->ai_canonname ||
            hints->ai_addr || hints->ai_next)
            ERR(EAI_BADHINTS); /* xxx */
        if (hints->ai_flags & ~AI_MASK)
            ERR(EAI_BADFLAGS);
        switch (hints->ai_family) {
        case PF_UNSPEC:
        case PF_INET:
#ifdef INET6
        case PF_INET6:
#endif
            break;
        default:
            ERR(EAI_FAMILY);
        }
        memcpy(pai, hints, sizeof(*pai));

        /*
         * if both socktype/protocol are specified, check if they
         * are meaningful combination.
         */
        if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
            for (ex = explore; ex->e_af >= 0; ex++) {
                if (pai->ai_family != ex->e_af)
                    continue;
                if (ex->e_socktype == ANY)
                    continue;
                if (ex->e_protocol == ANY)
                    continue;
                if (pai->ai_socktype == ex->e_socktype
                 && pai->ai_protocol != ex->e_protocol) {
                    ERR(EAI_BADHINTS);
                }
            }
        }
    }

    /*
     * check for special cases.  (1) numeric servname is disallowed if
     * socktype/protocol are left unspecified. (2) servname is disallowed
     * for raw and other inet{,6} sockets.
     */
    if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
#ifdef PF_INET6
     || MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
#endif
        ) {
        ai0 = *pai; /* backup *pai */

        if (pai->ai_family == PF_UNSPEC) {
#ifdef PF_INET6
            pai->ai_family = PF_INET6;
#else
            pai->ai_family = PF_INET;
#endif
        }
        error = get_portmatch(pai, servname);
        if (error)
            ERR(error);

        *pai = ai0;
    }

    ai0 = *pai;

    /* NULL hostname, or numeric hostname */
    for (ex = explore; ex->e_af >= 0; ex++) {
        *pai = ai0;

        /* PF_UNSPEC entries are prepared for DNS queries only */
        if (ex->e_af == PF_UNSPEC)
            continue;

        if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex)))
            continue;
        if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex)))
            continue;
        if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex)))
            continue;

        if (pai->ai_family == PF_UNSPEC)
            pai->ai_family = ex->e_af;
        if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
            pai->ai_socktype = ex->e_socktype;
        if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
            pai->ai_protocol = ex->e_protocol;

        if (hostname == NULL)
            error = explore_null(pai, servname, &cur->ai_next);
        else
            error = explore_numeric_scope(pai, hostname, servname,
                &cur->ai_next);

        if (error)
            goto free;

        while (cur->ai_next)
            cur = cur->ai_next;
    }

    /*
     * XXX
     * If numeric representation of AF1 can be interpreted as FQDN
     * representation of AF2, we need to think again about the code below.
     */
    if (sentinel.ai_next)
        goto good;

    if (hostname == NULL)
        ERR(EAI_NODATA);
    if (pai->ai_flags & AI_NUMERICHOST)
        ERR(EAI_NONAME);

        /*
         * BEGIN ANDROID CHANGES; proxying to the cache
         */
    //如果是Netd进程调用,则判断不成立,因为两个都返回0
    //如果非Netd进程调用,则判断成立
    if (cache_mode == NULL || strcmp(cache_mode, "local") != 0) {
        // we're not the proxy - pass the request to them
        return android_getaddrinfo_proxy(hostname, servname, hints, res, netid);
    }

    /*
     * hostname as alphabetical name.
     * we would like to prefer AF_INET6 than AF_INET, so we'll make a
     * outer loop by AFs.
     */
    for (ex = explore; ex->e_af >= 0; ex++) {
        *pai = ai0;

        /* require exact match for family field */
        if (pai->ai_family != ex->e_af)
            continue;

        if (!MATCH(pai->ai_socktype, ex->e_socktype,
                WILD_SOCKTYPE(ex))) {
            continue;
        }
        if (!MATCH(pai->ai_protocol, ex->e_protocol,
                WILD_PROTOCOL(ex))) {
            continue;
        }

        if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
            pai->ai_socktype = ex->e_socktype;
        if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
            pai->ai_protocol = ex->e_protocol;

        error = explore_fqdn(pai, hostname, servname,
            &cur->ai_next, netid, mark);

        while (cur && cur->ai_next)
            cur = cur->ai_next;
    }

    /* XXX */
    if (sentinel.ai_next)
        error = 0;

    if (error)
        goto free;
    if (error == 0) {
        if (sentinel.ai_next) {
 good:
            *res = sentinel.ai_next;
            return SUCCESS;
        } else
            error = EAI_FAIL;
    }
 free:
 bad:
    if (sentinel.ai_next)
        freeaddrinfo(sentinel.ai_next);
    *res = NULL;
    return error;
}

非Netd进程调用时,会执行函数 android_getaddrinfo_proxy。


static int
android_getaddrinfo_proxy(
    const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res, unsigned netid)
{
    int sock;
    const int one = 1;
    struct sockaddr_un proxy_addr;
    FILE* proxy = NULL;
    int success = 0;

    // Clear this at start, as we use its non-NULLness later (in the
    // error path) to decide if we have to free up any memory we
    // allocated in the process (before failing).
    *res = NULL;

    // Bogus things we can't serialize.  Don't use the proxy.  These will fail - let them.
    if ((hostname != NULL &&
         strcspn(hostname, " \n\r\t^'\"") != strlen(hostname)) ||
        (servname != NULL &&
         strcspn(servname, " \n\r\t^'\"") != strlen(servname))) {
        return EAI_NODATA;
    }

    //建立和Netd中 DnsProxyListener 的连接,将请求转发给它去执行
    sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
    if (sock < 0) {
        return EAI_NODATA;
    }

    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    memset(&proxy_addr, 0, sizeof(proxy_addr));
    proxy_addr.sun_family = AF_UNIX;
    strlcpy(proxy_addr.sun_path, "/dev/socket/dnsproxyd",
        sizeof(proxy_addr.sun_path));
    if (TEMP_FAILURE_RETRY(connect(sock,
                       (const struct sockaddr*) &proxy_addr,
                       sizeof(proxy_addr))) != 0) {
        close(sock);
        return EAI_NODATA;
    }

    netid = __netdClientDispatch.netIdForResolv(netid);

    // Send the request.
    //发送请求,处理回复等
    proxy = fdopen(sock, "r+");
    if (fprintf(proxy, "getaddrinfo %s %s %d %d %d %d %u",
            hostname == NULL ? "^" : hostname,
            servname == NULL ? "^" : servname,
            hints == NULL ? -1 : hints->ai_flags,
            hints == NULL ? -1 : hints->ai_family,
            hints == NULL ? -1 : hints->ai_socktype,
            hints == NULL ? -1 : hints->ai_protocol,
            netid) < 0) {
        goto exit;
    }
    // literal NULL byte at end, required by FrameworkListener
    if (fputc(0, proxy) == EOF ||
        fflush(proxy) != 0) {
        goto exit;
    }

    char buf[4];
    // read result code for gethostbyaddr
    if (fread(buf, 1, sizeof(buf), proxy) != sizeof(buf)) {
        goto exit;
    }

    int result_code = (int)strtol(buf, NULL, 10);
    // verify the code itself
    if (result_code != DnsProxyQueryResult ) {
        fread(buf, 1, sizeof(buf), proxy);
        goto exit;
    }

    struct addrinfo* ai = NULL;
    struct addrinfo** nextres = res;
    while (1) {
        uint32_t addrinfo_len;
        if (fread(&addrinfo_len, sizeof(addrinfo_len),
              1, proxy) != 1) {
            break;
        }
        addrinfo_len = ntohl(addrinfo_len);
        if (addrinfo_len == 0) {
            success = 1;
            break;
        }

        if (addrinfo_len < sizeof(struct addrinfo)) {
            break;
        }
        struct addrinfo* ai = calloc(1, addrinfo_len +
                         sizeof(struct sockaddr_storage));
        if (ai == NULL) {
            break;
        }

        if (fread(ai, addrinfo_len, 1, proxy) != 1) {
            // Error; fall through.
            break;
        }

        // Zero out the pointer fields we copied which aren't
        // valid in this address space.
        ai->ai_addr = NULL;
        ai->ai_canonname = NULL;
        ai->ai_next = NULL;

        // struct sockaddr
        uint32_t addr_len;
        if (fread(&addr_len, sizeof(addr_len), 1, proxy) != 1) {
            break;
        }
        addr_len = ntohl(addr_len);
        if (addr_len != 0) {
            if (addr_len > sizeof(struct sockaddr_storage)) {
                // Bogus; too big.
                break;
            }
            struct sockaddr* addr = (struct sockaddr*)(ai + 1);
            if (fread(addr, addr_len, 1, proxy) != 1) {
                break;
            }
            ai->ai_addr = addr;
        }

        // cannonname
        uint32_t name_len;
        if (fread(&name_len, sizeof(name_len), 1, proxy) != 1) {
            break;
        }
        name_len = ntohl(name_len);
        if (name_len != 0) {
            ai->ai_canonname = (char*) malloc(name_len);
            if (fread(ai->ai_canonname, name_len, 1, proxy) != 1) {
                break;
            }
            if (ai->ai_canonname[name_len - 1] != '\0') {
                // The proxy should be returning this
                // NULL-terminated.
                break;
            }
        }

        *nextres = ai;
        nextres = &ai->ai_next;
        ai = NULL;
    }

    if (ai != NULL) {
        // Clean up partially-built addrinfo that we never ended up
        // attaching to the response.
        freeaddrinfo(ai);
    }
exit:
    if (proxy != NULL) {
        fclose(proxy);
    }

    if (success) {
        return 0;
    }

    // Proxy failed;
    // clean up memory we might've allocated.
    if (*res) {
        freeaddrinfo(*res);
        *res = NULL;
    }
    return EAI_NODATA;
}

当非Netd进程调用 getaddrinfo 时,会调用函数 android_getaddrinfo_proxy, 它将和Netd进程中的 dnsproxyd 监听 socket建立连接,然后把请求发给 DNSProxyListener去执行。当然最后还是会调回到 getaddrinfo 去执行,而此时就变成了 Netd进程的调用了,Netd进程将向指定的 DNS 服务器发起请求以解析域名。 Android系统中通过这种方式来管理DNS的好处是,所有解析后得到的 DNS 记录都将缓存在 Netd 进程中,从而使这些信息成为一个公共的资源,最大程度做到信息共享。

MDnsSdListener分析

MDnsSd是Multicast DNS Service Discovery的简称,它和Apple公司的Bonjour技术有关,故本节将先介绍Apple Bonjour技术。 Bonjour是法语中的Hello之意。它是Apple公司为基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。使用Bonjour的设备在网络中自动组播它们自己的服务信息并监听其他设备的服务信息,设备之间就像在打招呼,这也是该技术命名为Bonjour的原因。Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。 举一个简单的例子:在局域网中,如果要进行打印服务,就必须先知道打印服务器的IP地址。此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务,名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机。在Bonjour的帮助下,客户端最终能找到这台注册了“print service”名字的打印机,并获得它的IP地址以及端口号。 从Bonjour角度来看,该技术主要解决了三个问题:

  • Addressing:即为主机分配IP。Bonjour的Addressing处理比较简单,即每个主机在网络内部的地址可选范围内找一个IP,然后查看下网络内部是否有其他主机再用。如果该IP没有被分配的话,它将使用此IP。
  • Naming:Naming解决的就是host和IP地址的对应关系。Bonjour采用的是Multiple DNS技术,即DNS查询消息将通过UDP组播方式发送。一旦网络内部某个机器发现查询的机器名和自己设置的一样,就回复这条请求。此外,Bonjour还拓展了MDNS的用途,即除了能查找host外,还支持对service的查找。不过,Bonjour的Naming有一个限制,即网络内部不能有重名的host或service。
  • Service Discovery:SD基于上面的Naming工作,它使得应用程序能查找到网络内部的服务,并解析该服务对应的IP地址和端口号。应用程序一旦得到服务的IP地址和端口号,就可以直接和该服务建立交互关系。

MDnsSdListener 对应的 Framework 层服务为 NsdService(Nsd为 Network Service Discovery的缩写)。 MDnsSdListener的内部类 Monitor 用于和 mdnsd 后台进程通信,它将调用Bonjour AIP。 Monitor 内部针对每个 DNSService 都会建立一个 Element 对象,该对象通过 Monitor 的 mHead 指针保存在一个list中。 MDnsSdListener 运行过程主要可分成三步:

  • 1.Netd创建 MDnsSdListener对象,其内部会创建 Monitor对象,而Monitor对象将启动一个线程用于和 mdnsd通信,并接收来自Handler的请求。
  • 2.NsdService 启动完毕后将向 MDnsSdListener发送 "start-service"命令。
  • 3.NsdService 响应应用程序的请求,向 MDnsSdListener 发送其他命令,例如 "discovery" 等。Monitor将最终处理这些请求。
  • 先看第一步,当 MDnsSdListener 构造时,会创建一个 Monitor 对象: android-5.1/system/netd/server/MDnsSdListener.cpp
MDnsSdListener::Monitor::Monitor() {
    mHead = NULL;
    mLiveCount = 0;
    mPollFds = NULL;
    mPollRefs = NULL;
    mPollSize = 10;
    //创建两个socket,用于接收 MDnsSdListener 对象的命令
    socketpair(AF_LOCAL, SOCK_STREAM, 0, mCtrlSocketPair);
    pthread_mutex_init(&mHeadMutex, NULL);

    //创建线程,线程函数是 threadStart,其内部会调用 run
    pthread_create(&mThread, NULL, MDnsSdListener::Monitor::threadStart, this);
    pthread_detach(mThread);
}

Monitor的threadStart线程将调用其 run 函数,该函数通过 poll 方式侦听包括 mCtrlSocketPair 在内的 socket 信息。 当 NsdService 发送 start-service 命令后, Handler 的 runCommand 将执行 Monitor 的 startService 函数: android-5.1/system/netd/server/MDnsSdListener.cpp

int MDnsSdListener::Monitor::startService() {
    int result = 0;
    char property_value[PROPERTY_VALUE_MAX];
    pthread_mutex_lock(&mHeadMutex);
    //MDNS_SERVICE_STATUS是一个字符串,值为"init.svc.mdnsd",在 init.rc 配置文件中, mdnsd是一个service,而"init.svc.mdnsd"将记录mdnsd进程的运行状态。
    property_get(MDNS_SERVICE_STATUS, property_value, "");
    if (strcmp("running", property_value) != 0) {
        ALOGD("Starting MDNSD");
        //如果 mdnsd 的状态不为 running,则通过设置 ctl.start 命令启动 mdnsd
        property_set("ctl.start", MDNS_SERVICE_NAME);
        //如果mdnsd成功启动,则属性变为running
        wait_for_property(MDNS_SERVICE_STATUS, "running", 5);
        result = -1;
    } else {
        result = 0;
    }
    pthread_mutex_unlock(&mHeadMutex);
    return result;
}

startService的实现比较有趣,充分利用了init的属性控制以启动 mdnsd 进程。 当 NsdService 发送注册服务请求时, Handler 的 serviceRegister 函数将被调用: android-5.1/system/netd/server/MDnsSdListener.cpp

void MDnsSdListener::Handler::serviceRegister(SocketClient *cli, int requestId,
        const char *interfaceName, const char *serviceName, const char *serviceType,
        const char *domain, const char *host, int port, int txtLen, void *txtRecord) {
    if (VDBG) {
        ALOGD("serviceRegister(%d, %s, %s, %s, %s, %s, %d, %d, <binary>)", requestId,
                interfaceName, serviceName, serviceType, domain, host, port, txtLen);
    }
    Context *context = new Context(requestId, mListener);
    DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);
    port = htons(port);
    if (ref == NULL) {
        ALOGE("requestId %d already in use during register call", requestId);
        cli->sendMsg(ResponseCode::CommandParameterError,
                "RequestId already in use during register call", false);
        return;
    }
    DNSServiceFlags nativeFlags = 0;
    int interfaceInt = ifaceNameToI(interfaceName);
    //调用 Bonjour API DNSServiceRegister,并注册回调函数 MDnsSdListenerRegisterCallback
    DNSServiceErrorType result = DNSServiceRegister(ref, interfaceInt, nativeFlags, serviceName,
            serviceType, domain, host, port, txtLen, txtRecord, &MDnsSdListenerRegisterCallback,
            context);
    if (result != kDNSServiceErr_NoError) {
        ALOGE("service register request %d got an error from DNSServiceRegister %d", requestId,
                result);
        mMonitor->freeServiceRef(requestId);
        cli->sendMsg(ResponseCode::CommandParameterError,
                "serviceRegister request got an error from DNSServiceRegister", false);
        return;
    }
    调用 Monitor 对象进行 rescan
    mMonitor->startMonitoring(requestId);
    if (VDBG) ALOGD("serviceRegister successful");
    cli->sendMsg(ResponseCode::CommandOkay, "serviceRegister started", false);
    return;
}

DNSServiceRegister 内部将把请求发送给 mdnsd 去处理,处理的结果通过 MDnsSdListenerRegisterCallback 返回:

void MDnsSdListenerRegisterCallback(DNSServiceRef /* sdRef */, DNSServiceFlags /* flags */,
        DNSServiceErrorType errorCode, const char *serviceName, const char * /* regType */,
        const char * /* domain */, void *inContext) {
    MDnsSdListener::Context *context = reinterpret_cast<MDnsSdListener::Context *>(inContext);
    char *msg;
    int refNumber = context->mRefNumber;
    if (errorCode != kDNSServiceErr_NoError) {
        asprintf(&msg, "%d %d", refNumber, errorCode);
        context->mListener->sendBroadcast(ResponseCode::ServiceRegistrationFailed, msg, false);
        if (DBG) ALOGE("register failure for %d, error= %d", refNumber, errorCode);
    } else {
        char *quotedServiceName = SocketClient::quoteArg(serviceName);
        asprintf(&msg, "%d %s", refNumber, quotedServiceName);
        free(quotedServiceName);
        //将处理结果返回给 NsdService
        context->mListener->sendBroadcast(ResponseCode::ServiceRegistrationSucceeded, msg, false);
        if (VDBG) ALOGD("register succeeded for %d as %s", refNumber, serviceName);
    }
    free(msg);
}

总结

Netd的处理流程:

  • 1、NM接收 Kernel 的UEvent消息,然后转发给 Framework 层的客户端
  • 2、CL、DPL以及 MDnsSdListener 接收来自客户端的请求并处理它们。

引用

Netd工作流程

Android服务之网络服务发现服务解析

Android Says Bonjour

Network Daemon(Android Netd)架构和源码分析