《深刻理解Android:Wi-Fi,NFC和GPS》章节连载[节选]--第二章 深刻理解Netd

首先感谢各位兄弟姐妹们的耐心等待。本书预计在3月中旬上市发售。从今天开始,我将在博客中连载此书的一些内容。注意,此处连载的是未经出版社编辑的原始稿件,因此样子会有些非专业。html

注意,以下是本章目录,本文节选2.1-2.3以及2.5节linux

 

 

为了方便读者深刻学习,本系列连载都会将做者研究过android

 

 

程中所学习的参考文献列出来数据库

 

 

 

第2章 深刻理解Netd编程

 

 

本章主要内容

  • 介绍Netd;
  • 介绍MDNSApple Bonjour技术;
  • 介绍iptablestcipLinux系统中经常使用的网络管理工具;
  • 介绍Netd中的各个命令对象和相关的背景知识;
  • 介绍NetworkManagmentService。

2.1  概述 

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

  • 设置防火墙(Firewall)、网络地址转换(NAT)、带宽控制、无线网卡软接入点(Soft Access Point)控制,网络设备绑定(Tether)等。
  • Android系统中DNS信息的缓存和管理。
  • 网络服务搜索(Net Service Discovery,简称NSD)功能,包括服务注册(Service Registration)、服务搜索(Service Browse)和服务名解析(Service Resolve)等。
  • Netd的工做流程和Vold相似[1],其工做可分红两部分:
  • Netd接收并处理来自Framework层中NetworkManagementServiceNsdService的命令。这些命令最终由Netd中对应的Command对象去处理。
  • Net接收并解析来自KernelUEvent消息,而后再转发给Framework层中对应Service去处理。

由上述内容可知,Netd位于Framework层和Kernel层之间,它是Android系统中网络相关消息和命令转发及处理的中枢模块。api

Netd的代码量不大,难度较低,但其所涉及的相关背景知识却比较多。本章对Netd的分析将从如下几个方面入手:数组

  • 首先介绍Netd的大致工做流程以及DNSMDns相关的背景知识。关于Netd的工做流程分析,读者也可参考中的内容。
  • 而后本章将集中介绍Netd中涉及到的Android系统中网络管理和控制的相关工具。它们是iptablestcip
  • 最后将介绍NetdCommandListener的命令处理。这些命令的正常工做依赖于上面介绍的iptables等工具。

最后,咱们将介绍Java Framework中的NetworkManagementService服务。缓存

提示:NsdService比较简单,感兴趣的读者不妨阅读做者的一篇博文”Android Says Bonjour”中的第2.2NsdService介绍”一节。地址位于http://blog.csdn.net/innost/article/details/8629139服务器

2.2 Netd工做流程分析

Netd进程由init进程根据init.rc的对应配置项[1]而启动,其配置项如图2-1所示。

2-1  Netd启动配置参数

由图2-1可知:

  • Netd启动时将建立三个TCP监听socket,其名称分别为"netd""dnsproxyd""mdns"

根据本章后续分析,读者将会看到:

  • Framework层中的NetworkManagementServiceNsdService将分别和"netd""mdns"监听socket创建连接并交互。
  • 每个调用和域名解析相关的socket API(如getaddrinfogethostbyname等)的进程都会借由"dnsproxyd"监听socketnetd创建连接。

下面开始分析Netd进程。

2.2.1  main函数分析

Netd进程的入口函数是其main函数,代码以下所示:

[-->main.cpp]

int main() {

 

    CommandListener *cl;

    NetlinkManager *nm;

    DnsProxyListener *dpl;

    MDnsSdListener *mdnsl;

 

    ALOGI("Netd 1.0 starting");

 

    //为Netd进程屏蔽SIGPIPE信号

    blockSigpipe();

 

   //①建立NetlinkManager

    nm = NetlinkManager::Instance();

    //②建立CommandListener,它将建立名为"netd"的监听socket

    cl = new CommandListener();

    //设置NetlinkManager的消息发送者(Broadcaster)为CommandListener。

    nm->setBroadcaster((SocketListener *) cl);

   //启动NetlinkManager

    nm->start();

   ......

    //注意下面这行代码,它为本Netd设置环境变量ANDROID_DNS_MODE为"local",其做用将在2.2.4节介绍

    setenv("ANDROID_DNS_MODE", "local", 1);

    //③建立DnsProxyListener,它将建立名为"dnsproxyd"的监听socket

    dpl = new DnsProxyListener();

    dpl->startListener();

 

   //④建立MDnsSdListener并启动监听,它将建立名为"mdns"的监听socket

    mdnsl = new MDnsSdListener();

    mdnsl->startListener();

 

   cl->startListener();

 

    while(1) {

        sleep(1000);

    }

    exit(0);

}

Netdmain函数很是简单,主要是建立几个重要成员并启动相应的工做,这几个重要成员分别是:

  • NetlinkManager:它将接收并处理来自KernelUEvent消息。这些消息经NetlinkManager解析后将借助它的Broadcaster(也就是代码中为NetlinkManager设置的CommandListener)发送给Framework层的NetworkManagementService
  • CommandListenerDnsProxyListenerMDnsSdListener:分别建立名为"netd""dnsproxyd""mdns"的监听socket,并处理来客户端的命令。

下面将分别讨论这四位成员的做用。

2.2.2  NetlinkManager分析

NetlinkManager(之后简称NM)主要负责接收并解析来自KernelUEvent消息。其核心代码在start函数中,以下所示。

 [-->NetlinkManager.cpp::start]

int NetlinkManager::start() {

  //建立接收NETLINK_KOBJECT_UEVENT消息的socket,其值保存在mUeventSock中

  //其中,NETLINK_FORMAT_ASCII表明UEvent消息的内容为ASCII字符串

  mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,

         0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII);

  //建立接收RTMGPR_LINK消息的socket,其值保存在mRouteSock中

  //其中,NETLINK_FORMAT_BINARY表明UEvent消息的类型为结构体,故须要进行二进制解析

  mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE, RTMGRP_LINK,

         NetlinkListener::NETLINK_FORMAT_BINARY);

  //建立接收NETLINK_NFLOG消息的socket,其值保存在mQuotaSock中

  mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG,

        NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY);

 return 0;

}

NMstart函数主要是向Kernel注册了三个用于接收UEvent事件的socket,这三个UEvent[1][2]分别对应于:

  • NETLINK_KOBJECT_UEVENT:表明kobject事件,因为这些事件包含的信息由ASCII字符串表达,故上述代码中使用了NETLINK_FOMRAT_ASCII。它表示将采用字符串解析的方法去解析接收到的UEvent消息。kobject通常用来通知内核中某个模块的加载或卸载。对NM来讲,其关注的是/sys/class/net下相应模块的加载或卸载消息。
  • NETLINK_ROUTE:表明kernelroutinglink改变时对应的消息。NETLINK_ROUTE包含不少子项,上述代码中使用了RTMGRP_LINK项。两者结合起来使用,表示NM但愿收到网络链路断开或接通时对应的UEvent消息(笔者在Ubuntu PC机上测试过,当网卡上拔掉或插入网线时,会触发这些UEvent消息的发送)。因为对应UEvent消息内部封装了nlmsghdr等相关结构体,故上述代码使用了NETLINK_FORMAT_BINARY来指示解析UEvent消息时将使用二进制的解析方法。
  • NETLINK_NFLOG:和2.3.6节介绍的带宽控制有关。Netd中的带宽控制能够设置一个预警值,当网络数据超过必定字节数就会触发kernel发送一个警告。该功能属于iptables的扩展项,但因为iptables的文档更新速度较慢(这也是不少开源项目的一大弊端),笔者一直未能找到相关的正式说明。值得指出的是,上述代码中有关NETLINK_NFLOG相关socket的设置并不是全部kernel版本都支持。同时,NFLOG_QUOTA_GROUP的值是直接定义在NetlinkManager.cpp中的,而非和其余相似系统定义同样定义在系统头文件中。这也代表NFLOG_QUOTA_GROUP的功能比较新。

提示:读者可经过在Linux终端中执行man PF_LINK获得有关NETLINK的详细说明。

上述start函数将调用setupSocket建立用于接收UEvent消息的socket以及一个解析对象NetlinkHandlersetupSocket代码自己比较简单,此处就不拟展开分析。

下面来看NM及其家族成员,它们之间的关系如图2-2所示。

2-2  NetlinkManager家族成员的类图

由图2-2可知:

  • NetlinkHandlerCommandListener均间接从SocketListener派生。其中,NetlinkHandler收到的socket消息将经过onEvent回调处理。
  • 结合前文所述,NetlinkManager分别注册了三个用于接收UEventsocket,其对应的NetlinkHandler分别是mUeventHandlermRouteHandlermQuotaHandler
  • NetlinkHandler接收到的UEvent消息会转换成一个NetlinkEvent对象。NetlinkEvent对象封装了对UEvent消息的解析方法。对于NETLINK_FOMRAT_ASCII类型,其parseAsciiNetlinkMessage函数会被调用,而对于NETLINK_FORMAT_BINARY类型,其parseBinaryNetlinkMessage函数会被调用。
  • NM处理流程的输入为一个解析后的NetlinkEvent对象。NM完成相应工做后,其处理结果将经由mBroadcaster对象传递给Framework层的接收者,也就是NetworkManagementService
  • CommandListenerFrameworkListener派生,而FrameworkListener内部有一个mCommands数组,它用来存储注册到FrameworkListener中的命令处理对象。

下面来简单了解下NetlinkHandleronEvent函数,因为其内部已针对不一样属性的NetlinkEvent进行了分类处理,故浏览这段代码能加深对前文所述不一样UEvent消息的做用的理解。

[-->NetlinkHandler.cpp::onEvent]

void NetlinkHandler::onEvent(NetlinkEvent *evt) {

    const char *subsys = evt->getSubsystem();

    ......

    //处理对应NETLINK_KOBJECT_UEVENT和NETLINK_ROUTE的信息

    if (!strcmp(subsys, "net")) {

        int action = evt->getAction();

        const char *iface = evt->findParam("INTERFACE");//查找消息中携带的网络设备名

        if (action == evt->NlActionAdd) {

            notifyInterfaceAdded(iface);//添加NIC(Network Interface Card)的消息

        } else if (action == evt->NlActionRemove) {

            notifyInterfaceRemoved(iface);//NIC被移除的消息

        } else if (action == evt->NlActionChange) {

            evt->dump();

            notifyInterfaceChanged("nana", true);//NIC变化消息

        } else if (action == evt->NlActionLinkUp) {//下面两个消息来自NETLINK_ROUTE

            notifyInterfaceLinkChanged(iface, true);//链路启用(相似插网线)

        } else if (action == evt->NlActionLinkDown) {

            notifyInterfaceLinkChanged(iface, false);//链路断开(相似拔网线)

        }

    } else if (!strcmp(subsys, "qlog")) {//对应NETLINK_NFLOG

        const char *alertName = evt->findParam("ALERT_NAME");

        const char *iface = evt->findParam("INTERFACE");

        notifyQuotaLimitReached(alertName, iface);//当数据量超过预警值,则会收到该通知

    } else if (!strcmp(subsys, "xt_idletimer")) {

        //这个和后文的idletimer有关,用于跟踪某个NIC的工做状态,便是“idle”仍是“active”

       //检测时间按秒计算

        int action = evt->getAction();

        const char *label = evt->findParam("LABEL");

        const char *state = evt->findParam("STATE");

        if (label == NULL) {

            label = evt->findParam("INTERFACE");

        }

        if (state)

            notifyInterfaceClassActivity(label, !strcmp("active", state));

    }

   ......

}

由上边代码可知:

  • NETLINK_KOBJECT_UEVENTNETLINK_ROUTE主要反映网络设备的事件和状态,包括NIC的添加、删除和修改,以及链路的链接状态等。
  • NETLINK_NFLOG用于反映设置的log是否超过配额。
  • 另外,上边代码中还处理了“xt_idletimer”的uevent消息,它和后文要介绍的IdleTimerCmd有关,主要用来监视网络设备的收发工做状态。当对应设备工做或空闲时间超过设置的监控时间后,Kernel将会发送携带其状态("idle""active")的UEvent消息。

2-3所示为NetlinkHandler的工做流程。

2-3  NM工做流程图

由图2-3可知:

  • NM建立NetlinkHandler后,工做便转交给NetlinkHandler来完成,而每一个NetlinkHandler对象均会单首创建一个线程用于接收Socket消息。
  • Kernel发送UEvent消息后,NetlinkHandler便从select调用中返回,而后调用其onDataAvailable函数,该函数内部会建立一个NetlinkEvent对象。
  • NetlinkEvent对象根据socket建立时指定的解析类型去解析来自KernelUEvent消息。
  • 最终NetlinkHandleronEvent将被调用,不一样的UEvent消息将在此函数中进行分类处理。
  • NetlinkHandler最终将处理结果经由NM内部变量mBroadcaster转发给NetworkManagementService
提醒:请读者结合上文所述流程自行研读相关代码。

[1]关于init工做原理以及init.rc的分析方法,读者可参考《深刻理解Android:卷1》第3章关于init进程的分析。


[1]读者可参考《深刻理解Android:卷1》第9章关于Vold的分析。

2.2.3  CommandListener分析

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

CL中的图2-4所示为CL中的Command对象及对应的Controller对象。

2-4  CL中的命令及控制类

由图2-4可知:

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

结合前面图2-2中对NM家族成员的介绍,CL建立时,须要注册本身支持的命令类。这部分代码在其构造函数中实现,代码以下所示:

[-->CommandListener::CommandListener构造函数]

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());

    //建立对应的控制类对象

    if (!sSecondaryTableCtrl)

        sSecondaryTableCtrl = new SecondaryTableController();

    if (!sTetherCtrl)

        sTetherCtrl = new TetherController();

    if (!sNatCtrl)

        sNatCtrl = new NatController(sSecondaryTableCtrl);

    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();

    //其余重要工做,后文再分析

}

因为CL的间接基类也是SocketListener,因此其工做流程和NetlinkHandler相似。

为了方便读者理解,图2-5给出了CL的工做流程图:

2-5  CL的工做流程示意图

图2-5中,假设Client端发送的命令名是"nat",当CL收到这个命令后,首先会从其构造函数中注册的那些命令对象中找到对应该名字(即"nat")的命令对象,其结果就是图中的NatCmd对象。而该命令最终的处理工做将由此NatCmd对象的runCommand函数完成。

2.2.4 DnsProxyListener分析

DnsProxyListenerAndroid系统中的DNS管理有关。什么是DNS呢?Android系统中DNS又有什么特色呢?来看下文。

1. Android DNS介绍[3]

DNS是Domain Name System(域名系统)的缩写。其主要目的是在域名和IP地址之间创建一种映射。简单点说,DNS的功能相似于电话簿,它可将人名映射到相应的电话号码。在DNS中,人名就是域名,电话号码就是IP地址。域名系统的管理由DNS服务器来完成。全球范围内的DNS服务器共同构成了一个分布式的域名-IP数据库。

对使用域名来发起网络操做的网络程序来讲,其域名解析工做主要分两步:

1)第一步工做就是须要将域名转换成IP。因为域名和IP的转换关系存储在DNS服务器上,因此该网络程序要向DNS服务器发起请求,以获取域名对应的IP地址。

2DNS服务器根据DNS解析规则解析并获得该域名对应的IP地址,而后返回给客户端。在DNS中,每个域名和IP的对应关系被称之为一条记录。客户端通常会缓存这条记录以备后续之用。

提醒:DNS解析规则比较复杂,感兴趣的读者可研究DNS的相关协议。

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

  • getaddrinfo:它根据指定的host名或service名获得对应的IP地址(该IP地址由结构体addrinfo表达)。
  • getnameinfo:根据指定的IP地址(由结构体sockaddr表达)获得对应的hostservice的名称。

Android中,这两个函数均由Bionic C实现。其代码实现基于NetBSD的解析库(resolver library),并通过一些修改。这些修改包括:

  • 没有实现name-server-switch功能。这是为了保持Bionic C库的轻便性而作的裁剪。
  • DNS服务器的配置文件由/etc/resolv.conf变成/system/etc/resolv.conf[1]。在Android系统中,/etc目录实际上为/system/etc目录的连接。resolv.conf存储的是DNS服务器的IP地址。
  • 系统属性中保存了一些DNS服务器的地址,它们经过诸如"net.dns1""net.dns2"之类的属性来表达。这些属性由dhcpd进程或其余系统模块负责维护。
  • 每一个进程还能够设置进程特定的DNS服务器地址。它们经过诸如"net.dns1.<pid>""net.dns2.<pid>"的系统属性来表达。
  • 不一样的网络设备也有对应的DNS服务器地址,例如经过wlan接口发起的网络操做,其对应的DNS服务器由系统属性“net.wlan.dns1”表示。

2-6所示为三星Galaxy Note2中有关dns信息的示意图。

2-6  net.dns设置示意图

由图2-6可知:

  • 系统中有些进程有本身特定的DNS服务器。
  • 不一样网络设备也设置了对应的DNS服务器地址。

2. getaddrinfo函数分析

本节将介绍Android中getaddrinfo的实现,咱们将只关注Android对其作的改动。

[-->getaddrinfo.c::getaddrinfo]

int  getaddrinfo(const char *hostname, const char *servname,

    const struct addrinfo *hints, struct addrinfo **res)

{

    ......//getaddrinfo的正常处理

   //Android平台的特殊定制

   if (android_getaddrinfo_proxy(hostname, servname, hints, res) == 0) {

        return 0;

   }

   ......//若是上述函数处理失败,则继续getaddrinfo的正常处理

  return error

}

由上述代码可知,Android平台中的getaddrinfo会调用其定制的android_getaddrinfo_proxy函数完成一些特殊操做,该函数的实现以下所示:

[-->getaddrinfo.c::android_getaddrinfo_proxy]

static int android_getaddrinfo_proxy(const char *hostname, const char *servname,

               const struct addrinfo *hints, struct addrinfo **res)

{

    .......

    //取ANDROID_DNS_MODE环境变量。只有Netd进程设置了它

    const char* cache_mode = getenv("ANDROID_DNS_MODE");

    ......

   //因为Netd进程设置了此环境变量,故Netd进程调用getaddrinfo的话,将不会采用这套定制的方法

    if (cache_mode != NULL && strcmp(cache_mode, "local") == 0) {

        return -1;

    }

   //获取本进程对应的DNS地址

    snprintf(propname, sizeof(propname), "net.dns1.%d", getpid());

    if (__system_property_get(propname, propvalue) > 0) {

        return -1;

    }

 

    //创建和Netd中DnsProxyListener的链接,将请求转发给它去执行

    sock = socket(AF_UNIX, SOCK_STREAM, 0);

    if (sock < 0) {

        return -1;

    }

   ......

    strlcpy(proxy_addr.sun_path, "/dev/socket/dnsproxyd",

          sizeof(proxy_addr.sun_path));

    ......//发送请求,处理回复等

    return -1;

}

由上述代码可知:

  • Netd进程调用getaddrinfo时,因为其设置了ANDROID_DNS_MODE环境变量,因此该函数会继续原来的流程。
  • 当非Netd进程调用getaddrinfo函数时,首先会开展android_getaddrinfo_proxy中的工做,即判断该进程是否有定制的DNS服务器,若是没有的话它将和位于Netd进程中的"dnsproxyd"监听socket创建链接,而后把请求发给DnsProxyListener去执行。

3. DnsProxyListener命令介绍

下面来介绍DnsProxyListener(之后简称DPL),图2-7所示为其家族成员示意图:

2-7  DPL家族示意图

由图2-7可知,DPL仅定义了两个命令:

  • GetAddrInfoCmd,和Bionic C库的getaddrinfo函数对应。
  • GetHostByAddrCmd,和Bionic C库的gethostbyaddr函数对应。

这个两条命令的处理比较简单,此处就不拟展开详细的代码。

为方便读者理解,咱们将给出调用序列图,如图2-8所示。

2-8  GetAddrInfoCmd处理流程示意图

由图2-8所示,GetAddrInfoHandler最终的处理仍是交由Bionic Cgetaddrinfo函数来完成。根据前文所述,因为Netd进程设置了ANDROID_DNS_MODE环境变量,故Netd调用的getaddrinfo将走正常的流程。这个正常流程就是Netd进程将向指定的DNS服务器发起请求以解析域名。

Android系统中,经过这种方式来管理DNS的好处是全部解析后获得的DNS记录都将缓存在Netd进程中,从而使这些信息成为了一个公共的资源,最大程度内作到了信息共享。

 

2.2.5 MDnsSdListener分析

 

MDnsSdMulticast DNS Service Discovery的简称,它和Apple公司的Bonjour技术有关,故本节将先介绍Apple Bonjour技术。

1. Apple Bonjour技术介绍[4][5][6]

Bonjour是法语中的Hello之意。它是Apple公司为基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。使用Bonjour的设备在网络中自动组播它们本身的服务信息并监听其余设备的服务信息,设备之间就像在打招呼,这也是该技术命名为Bonjour的缘由。Bonjour使得局域网中的系统和服务即便在没有网络管理员的状况下也很容易被找到。

举一个简单的例子:在局域网中,若是要进行打印服务,就必须先知道打印服务器的IP地址。此IP地址通常由IT部门的人负责分配,而后他还得全员发邮件以公示此地址。有了Bonjour之后,打印服务器本身会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务,名为“print service”之类的。当客户端须要打印服务时,会先搜索网络内部的打印服务器。因为不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机。在Bonjour的帮助下,客户端最终能找到这台注册了“print service”名字的打印机,并得到它的IP地址以及端口号。

Bonjour角度来看,该技术主要解决了三个问题:

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

Bonjour技术在Mac OS以及ItunesIphone上都获得了普遍应用。为了进一步推广,Apple经过开源工程mdnsresponder将其开源出来。在Windows平台上,它将生成一个后台程序mdnsresponder。在Android平台上(或者说支持POSIXLinux平台)它是一个名为mdnsd的程序。不过,不管是mdnsresponder仍是mdnsd,应用开发者要作的仅仅是利用BonjourAPI向它们发起服务注册、服务查询和服务解析等请求并接收来自它们的处理结果。

下面咱们将介绍Bonjour API中使用最多的三个函数,它们分别是服务注册、服务查询和服务解析。理解这三个函数的功能也是理解MDnsSdListener的基础。

使用Bonjour API必须包含以下的头文件和动态库,并链接到:

#include <dns_sd.h>  //必须包含此头文件

libmdnssd.so  //连接到此so

Bonjour中,服务注册的APIDNSServiceRegister,原型以下:

DNSServiceErrorType DNSSD_API DNSServiceRegister

(

    DNSServiceRef                       *sdRef,

    DNSServiceFlags                     flags,

    uint32_t                            interfaceIndex,

    const char                          *name,         /* may be NULL */

    const char                          *regtype,

    const char                          *domain,       /* may be NULL */

    const char                          *host,         /* may be NULL */

    uint16_t                            port,          /* In network byte order */

    uint16_t                            txtLen,

    const void                          *txtRecord,    /* may be NULL */

    DNSServiceRegisterReply             callBack,      /* may be NULL */

    void                                *context       /* may be NULL */

);

该函数的解释以下:

  • sdRef:表明一个未初始化的DNSService实体。其类型DNSServiceRef是指针。该参数最终由DNSServiceRegister函数分配内存并初始化。
  • flags:表示当网络内部有重名服务时的冲突处理。默认是按顺序修改服务名。例如要注册的服务名为“printer”,当检测到重名冲突时,就可更名为“printer(1)”。
  • interfaceIndex:表示该服务输出到主机的哪些网络接口上。值-1表示仅对本机支持,也就是该服务的用在loop接口上。
  • name:表示服务名,为空的话就取机器名。
  • regtype:服务类型,用字符串表达。Bonjour要求格式为"_服务名._传输协议",例如"_ftp._tcp"。目前传输协议仅支持TCPUDP
  • domianhost通常都为空。
  • port表示该服务的端口。若是为0的话,Bonjour会自动分配一个。
  • txtLen以及txtRecord字符串用来描述该服务。通常都设置为空。
  • callBack:设置回调函数。该服务注册的请求结果都会经过它回调给客户端。
  • context:上下文指针,由应用程序设置。

当客户端须要搜索网络内部特定服务时,须要使用DNSServiceBrowser API,其原型以下:

DNSServiceErrorType DNSSD_API DNSServiceBrowse

(

    DNSServiceRef                       *sdRef,

    DNSServiceFlags                     flags,

    uint32_t                            interfaceIndex,

    const char                          *regtype,

    const char                          *domain,    /* may be NULL */

    DNSServiceBrowseReply               callBack,

    void                                *context    /* may be NULL */

);

其中:

  • sdrefinterfaceIndexregtypedomain以及context含义与DNSServiceRegister同样。
  • flags:在本函数中没有做用。
  • callBack:为DNSServiceBrowser处理结果的回调通知接口。

当客户端想得到指定服务的IP和端口号时,须要使用DNSServiceResolve API,其原型以下:

DNSServiceErrorType DNSSD_API DNSServiceResolve

(

    DNSServiceRef                       *sdRef,

    DNSServiceFlags                     flags,

    uint32_t                            interfaceIndex,

    const char                          *name,

    const char                          *regtype,

    const char                          *domain,

    DNSServiceResolveReply              callBack,

    void                                *context  /* may be NULL */

);

其中:

  • nameregtypedomain都从DNSServiceBrowse函数的处理结果中得到。
  • callBack用于通知DNSServiceResolve的处理结果。该回调函数将返回服务的IP地址和端口号。

2. MDnsSdListener分析

MDnsSdListener对应的Framework层服务为NsdServiceNsdNetwork Service Discovery的缩写),它是Android 4.1新增的一个FrameworkService。该服务的实现比较简单,故本书不拟详细讨论它。感兴趣的读者不妨首先阅读SDK中关于NsdService的相关文档。

提示:SDK中有一个基于Nsd技术开发的NsdChat例程,读者也可先学习它的实现。相关文档位置为http://developer.android.com/training/connect-devices-wirelessly/nsd.html

2-9所示为MDnsSdListener的家族成员示意图。

2-9  MDnsSdListener家族成员

由图2-9可知:

  • MDnsSdListener的内部类Monitor用于和mdnsd后台进程通讯,它将调用前面提到的Bonjour API
  • Monitor内部针对每一个DNSService都会创建一个Element对象,该对象经过MonitormHead指针保存在一个list中。
  • HandlerMDnsSdListener注册的Command

下面将简单介绍MDnsSdListener的运行过程,主要工做可分红三步:

1Netd建立MDnsSdListener对象,其内部会建立Monitor对象,而Monitor对象将启动一个线程用于和mdnsd通讯,并接收来自Handler的请求。

2NsdService启动完毕后将向MDnsSdListener发送"start-service"命令。

3NsdService响应应用程序的请求,向MDnsSdListener发送其余命令,例如"discovery"等。Monitor将最终处理这些请求。

先来看第一步,当MDnsSdListener构造时,会建立一个Monitor对象,代码以下所示:

[-->MDnsSdListener.cpp::Monitor:Monitor]

MDnsSdListener::Monitor::Monitor() {

    mHead = NULL;

    pthread_mutex_init(&mHeadMutex, NULL);

    //建立两个socket,用于接收MDnsSdListener对象的指令

    socketpair(AF_LOCAL, SOCK_STREAM, 0, mCtrlSocketPair);

    //建立线程,线程函数是threadStart,其内部会调用run

    pthread_create(&mThread, NULL, MDnsSdListener::Monitor::threadStart, this);

}

MonitorthreadStart线程将调用其run函数,该函数经过poll方式侦听包括mCtrlSocketPair在内的socket信息。这部分代码属于基本的Linux socket编程,本书不拟开展深刻讨论。

NsdService发送"start-service"命令后,HandlerrunCommand将执行MonitorstartService函数,代码以下所示:

[-->MDnsSdListener.cpp::Monitor:startService]

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) {

         //若是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发送注册服务请求时,HandlerserviceRegister函数将被调用,代码以下所示:

[-->MDnsSdListener.cpp::Handler:serviceRegister]

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) {

    Context *context = new Context(requestId, mListener);

    DNSServiceRef *ref = mMonitor->allocateServiceRef(requestId, context);

    port = htons(port);

   ......

    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) {

        .....//错误处理

    }

    //通知Monitor对象进行rescan,请读者自行研究该函数

    mMonitor->startMonitoring(requestId);

    cli->sendMsg(ResponseCode::CommandOkay, "serviceRegister started", false);

    return;

}

DNSServiceRegister内部将把请求发送给mdnsd去处理,处理的结果经过MDnsSdListenerRegisterCallback返回,该函数代码以下所示:

[-->MDnsSdListener.cpp::MDnsSdListenerRegisterCallback]

void MDnsSdListenerRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags,

        DNSServiceErrorType errorCode, const char *serviceName, const char *regType,

        const char *domain, void *inContext) {

    MDnsSdListener::Context *context =

                einterpret_cast<MDnsSdListener::Context *>(inContext);

    char *msg;

    int refNumber = context->mRefNumber;

    if (errorCode != kDNSServiceErr_NoError) {

       ......//错误处理

    } else {

        char *quotedServiceName = SocketClient::quoteArg(serviceName);

        asprintf(&msg, "%d %s", refNumber, quotedServiceName);

        free(quotedServiceName);

       //将处理结果返回给NsdService

        context->mListener->sendBroadcast(ResponseCode::ServiceRegistrationSucceeded,

                                          msg,false);

    }

    free(msg);

}

 

提示   本节对Netd的工做流程进行了相关分析,这部分代码相对简单,处理流程也比较固定:

1NM接收KernelUEvent消息,而后转发给Framework层的客户端。

2CLDPL以及MDnsSdListener接收来自客户端的请求并处理它们。

惟一有趣的地方是AndroidDNS的管理以及Apple Bonjour技术。感兴趣的读者不妨阅读本章列出的参考资料以加深理解。


[1]此处结论来自bionic/libc/docs/OVERVIEW.txt文件,不过根据同目录下CHANGES.txt的说明,resolv.conf将再也不使用

====================================================================================

=========================略略略略略略略略略略略略略略==================================

2.5 本章总结和参考资料说明

2.5.1 本章总结

本章对Netd进行了详细讨论。相信读者读完此章的第一感觉必定是代码这么容易的模块,居然涉及如此多复杂的背景知识。确实,这也是专题卷所述内容的核心特色。从代码上看也许它们并不复杂,可是其背后的理论知识却可能大有来头。对于这些内容而言,代码只是外在的表现形式,其核心必定在其背后的那些知识中。因此,读者在阅读专题卷的时候,必定要考察本身是否对背景知识有所掌握。

概况而言,Netd涉及的内容和网络管理与控制有关,例如DNSApple Bonjour、利用iptables等工具实现NAT、防火墙、带宽控制、流量控制、路由控制功能,以及USB绑定Wi-FiSoftAP等。请读者在本节的参考资料一览中找到并继续研究本身感兴趣的内容。

最后,咱们对NetworkManagementService进行了介绍。NMService的内容很是简单。

2.5.2 参考资料说明

Linux PF_NETLINK相关资料

[1]  Linux man PF_NETLINK

本文档是Linux系统中的帮助文档。从整体上介绍了PF_NETLINKAF_NETLINK)的做用和相关的数据结构。对熟手比较适用。

[2]  http://www.linuxjournal.com/article/8498

Manipulating the Networking Environment Using RTNETLINK”,这篇文章以RTNETLINK为主要对象,介绍了如何利用它进行编程以操做网络。此文写得很是详细,建议读者深刻阅读,甚至本身动手写测试例子。

DNS、Apple Bonjour相关资料

[3]  http://baike.baidu.com/view/22276.htm 

百度百科中关于dns的介绍,属于入门级材料,不清楚的读者能够先了解相关知识。

[4]  http://en.wikipedia.org/wiki/MDNS 

维基百科中关于Multicast DNS的介绍。入门级材料,但包含的信息不是很全,须要跟踪其中的连接才能对MDNS有全面了解。

[5]  https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/NetServices/Introduction.html#//apple_ref/doc/uid/TP40002445-SW1 

Introduction to Bonjour Overview”,苹果开发网站上关于Bonjour基础知识的入口,包含“About Bonjour”、“Bonjour API Architecture”等文档。

[6]  https://developer.apple.com/library/mac/#documentation/Networking/Conceptual/dns_discovery_api/Introduction.html#//apple_ref/doc/uid/TP30000964 

DNS Service Discovery Programming Guide”,苹果开发网站关于NSD API的说明。

iptables相关资料

iptables的相关文档很是多,虽然Linux也提供了帮助文档(man iptables),但对新手来讲该文档实在不是学习的好资料。

[7]  http://www.thegeekstuff.com/2011/01/iptables-fundamentals/ 

Linux Firewall Tutorial: IPTables Tables, Chains, Rules Fundamentals”,这篇文章首先从原理上介绍了如何去理解iptables,而后介绍了相关的例子。笔者认为它是iptables最好的入门资料。

[8]  http://selboo.com.cn/post/721/ 

iptables的相关概念和数据包的流程”,这篇文档介绍了iptables中各个tablechain的处理顺序,请读者结合[7]来理解iptables

[9]  http://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html 

Iptables 指南 1.1.19”,这篇文档介绍的iptables版本比较旧(Android 4.2使用的iptables版本是1.4.11),但对iptables经常使用参数都有很是详细的介绍。适合入门后的读者进行深刻阅读。

TC相关资料

tc文献的数量和难度远大于iptables,此处精选几个必读文献。

[10]  http://linux-ip.net/articles/Traffic-Control-HOWTO/intro.html 

Traffic Control HOWTO”,理解traffic control的必读文献,覆盖面很全,理论知识讲解到位。难度稍大,须要仔细琢磨才能彻底理解。

[11]  http://wenku.baidu.com/view/f02078db50e2524de5187e45.html 

TC(Linux下流量控制工具)详细说明及应用实例”,百度文库中的一篇文档,篇幅虽然不长,但也作到了理论和实例结合。建议读者先阅读此文献,而后再深刻研究[10]

[12]  http://fanqiang.chinaunix.net/a1/b1/20010811/0705001103.html 

“在LINUX中实现流量控制器”,介绍TC的一篇博文,主要对tc的命令用法列举了很多实例,属于tc的实战文章。建议放到最后阅读。

[13]  http://www.linuxfoundation.org/collaborate/workgroups/networking/ifb 

这是笔者能找到的关于IFB设备最完整的资料,对IFB的使用、常规用法等进行了全方位的介绍。

IP命令相关资料

ip命令比较简单,这里仅给出一篇文献。

[14]  http://blog.chinaunix.net/uid-24921475-id-2547198.html 

Linux ip命令介绍

NetDevice编程文献

[15]  Linux man netdevice

很是详细的NetDevice编程介绍,建议读者认真阅读。

Linux策略路由相关资料

[16]  http://www.cnblogs.com/iceocean/articles/1594488.html 

Linux策略路由”,中文文档,知识面覆盖较全,属于入门级资料。

[17]  http://www.policyrouting.org/PolicyRoutingBook/ONLINE/TOC.html

Policy Routing With Linux”,这是一本完整的书籍(可见网管是一个复杂的工做)。我的感受[16]是参考[17]的学习总结。属于高级阅读材料,难度较大。

Linux IPv6控制相关资料

[18]  http://www.ipsidixit.net/2012/08/09/ipv6-temporary-addresses-and-privacy-extensions/ 

IPv6 temporary addresses and privacy extensions”,介绍LinuxIPv6临时地址和privacy extensions方面的知识,知识覆盖面较全。属于入门资料。

 

TTY和ptmx编程相关资料

[19]  http://tldp.org/HOWTO/Text-Terminal-HOWTO.html 

Text-Terminal-HOWTO”,比较旧的资料,覆盖面很是广。读者可仅阅读本身想了解的章节。

[20]  http://blog.tianya.cn/blogger/post_read.asp?BlogID=3616841&PostID=33399981 

Linuxtty/pty/pts/ptmx 详解”,中文写的好材料,还列出了其参考的文献。最后,关于ptmx,读者还可经过man ptmx得到如何用它进行编程的指导。

PPP和Pppd相关资料

[21]  http://tldp.org/HOWTO/PPP-HOWTO/ 

Linux PPP HOWTO”,Linux HowTo系列的内容都简单易懂。虽章节较多,但不少内容仅一两句了事。可作入门参考。

[22]  http://network.51cto.com/art/201009/223784.htm 

“基础解读PPP协议”,中文文档,一页内容,主要介绍PPP框架性的内容。

[23]  http://wenku.baidu.com/view/0c395f15866fb84ae45c8d4a.html 

ppp介绍”,百度文库中的一个关于pppPPT。内容翔实,不只介绍了ppp协议的数据包,也从框架上介绍了ppp的工做流程。建议读者首先阅读此文献。

[24]  Linux man pppd

介绍pppd中各个选项的做用。

NAT相关资料

[25]  http://oa.jmu.edu.cn/netoa/libq/pubdisc.nsf/66175841be38919248256e35005f4497/7762e8e1056be98f48256e88001ef71d?OpenDocument 

“用iptables实现NAT”,中文文档,简单易懂。

Tether、RNDIS、DHCP、DNSmasq相关资料

[26]  http://en.wikipedia.org/wiki/Tethering 

Tethering”,维基百科中关于Tether的介绍,浅显易懂,属于普及型资料。

[27]   http://msdn.microsoft.com/en-us/library/windows/hardware/gg463293.aspx 

Remote NDIS (RNDIS) and Windows”,MSDN文档,很是翔实(不得不说微软在文档方面的工做真的是一丝不苟)。

[28]  http://baike.baidu.com/view/7992.htm?subLemmaId=7992&fromenter=%A3%C4%A3%C8%A3%C3%A3%D0 

百度百科中关于DHCP的解释,入门资料。

[29]  http://baike.baidu.com/view/6681631.htm 

百度百科中关于DNSmasq的解释。

[30]  http://wenku.baidu.com/view/662b536b561252d380eb6ec1.html

关于DHCP协议中option字段的详细介绍。

Softap和hostapd相关资料

[31]  802.11 无线网络权威指南中文第二版》

读者可先阅读第12章中关于Wi-Fi技术中的一些基本概念,例如APStation

[32]  http://baike.baidu.com/view/2475889.htm 

百度百科关于SoftAp的入门级介绍。

[33]  关于hostapd,读者可利用man hostapd获得各个选项的用法。

提示,读者必须先安装hostapd,而后才能查阅其帮助文档。

相关文章
相关标签/搜索