Android-IpReachabilityMonitor


简介

本文主要介绍基于Android 7.0 的IpReachabilityMonitor机制


IpReachabilityMonitor简介

* Monitors on-link IP reachability and notifies callers whenever any on-link addresses of interest appear to have become unresponsive.

//监视链路IP的可达性;无论任何时候,一旦所关注的链路地址变的不响应时,就会通知调用者。

This code does not concern itself with "why" a neighbour might have become unreachable. Instead, it primarily reacts to the kernel's notion of IP reachability for each of the neighbours we know to be critically important to normal network connectivity. As such, it is often "just the messenger":the neighbours about which it warns are already deemed by the kernel to have become unreachable.

//IpReachabilityMonitor的代码实现逻辑不关心为什么一个neighbour网络变为不可达。相反,他主要反映在内核层面中每一个neighbour网络的IP可达性概念或状态。我们知道这个IP可达性状态对于正常网络连接来说是关键重要的。因此,IpReachabilityMonitor通常仅是“信使”:它警告neighbours网络已经被内核kernel视为不可达。


IpReachabilityMonitor工作方式

How it works:

1. The "on-link neighbours of interest" found in a given LinkProperties instance are added to a "watch list" via #updateLinkProperties().This usually means all default gateways and any on-link DNS servers.

//1、在给定的LinkProperties实例中找到关注的链路neighbours网络,这些neighbours网络被通过updateLinkProperties()方法添加到一个"watch list"中。这通常意味着所有默认网关和链路DNS服务器。

2. We listen continuously for netlink neighbour messages (RTM_NEWNEIGH,RTM_DELNEIGH), watching only for neighbours in the watch list.

//2、我们连续不断地监听netlink消息(RTM_NEWNEIGH,RTM_DELNEIGH),仅仅关注在watch list中的neighbours网络。

- A neighbour going into NUD_REACHABLE, NUD_STALE, NUD_DELAY, and even NUD_PROBE is perfectly normal; we merely record the new state.

//一个neighbour网络进入NUD_REACHABLE, NUD_STALE, NUD_DELAY和NUD_PROBE状态,均是完全正常现象,我们只是记录最新的状态。

- A neighbour's entry may be deleted (RTM_DELNEIGH), for example due to garbage collection.  This is not necessarily of immediate concern; we record the neighbour as moving to NUD_NONE.

//一个neighbour条目可能被删除(RTM_DELNEIGH),例如由于垃圾回收。这个没有必要进行立即关联。我们记录被搬移到NUD_NONE状态的neighbour网络。

- A neighbour transitioning to NUD_FAILED (for any reason) is  critically important and is handled as described below in #4.

//一个neighbour网络被转移到NUD_FAILED(任何原因)是极其重要的,这个相应的操作将会在下面#4描述。

3. All on-link neighbours in the watch list can be forcibly "probed" by calling #probeAll(). This should be called whenever it is important to verify that critical neighbours on the link are still reachable, e.g. when roaming between BSSIDs.

//3、可以通过调用probeAll()方法来强制执行probed所有在watch list 中的链路neighbours网络。不管什么时候,对于想验证链路上关键neighbour网络依然可达,这个方法是重要的,应该被调用,比如当在不同的BSSID之间Roaming的时候。

- The kernel will send unicast ARP requests for IPv4 neighbours and unicast NS packets for IPv6 neighbours.  The expected replies will likely be unicast.

//内核kernel将为IPv4 neighbour 网络发送单播ARP请求和为IPv6 neighbour网络发送单播NS数据包。预期的答复可能是单播。

- The forced probing is done holding a wakelock. The kernel may,however, initiate probing of a neighbor on its own, i.e. whenever a neighbour has expired from NUD_DELAY.

//强制probing 必须持有一个wakelock才可以执行。然而,内核kernel也许引发自己的一个neighbor
网络的probing,比如一个neighbour网络已经过期了(在进入NUD_DELAY状态后过期)。

- The kernel sends:

&1(/proc/sys/net/ipv{4,6}/neigh//ucast_solicit) number of probes (usually 3) every        &2(/proc/sys/net/ipv{4,6}/neigh//retrans_time_ms)number of milliseconds (usually 1000ms).

This normally results in 3 unicast packets, 1 per second.

//内核Kernel每&2秒发送&1次Probe 单播请求。通常结果是每1秒发送3次单播请求。

- If no response is received to any of the probe packets, the kernel marks the neighbour as being in state NUD_FAILED, and the listening process in #2 will learn of it.

//如果对于所有的Probe 请求,都没有接受到响应,内核kernel会将neighbour网络标记为NUD_FAILED状态,在#2中描述的监听进程将会学习它。

4. We call the supplied Callback#notifyLost() function if the loss of a neighbour in NUD_FAILED would cause IPv4 or IPv6 configuration to become incomplete (a loss of provisioning).

//4、如果处于NUD_FAILED状态的neighbour网络的丢失将会导致Ipv4 或者Ipv6配置变得不完整(配置丢失),我们将会调用提供的Callback#notifyLost()函数。

- For example, losing all our IPv4 on-link DNS servers (or losing our only IPv6 default gateway) constitutes a loss of IPv4 (IPv6) provisioning; Callback#notifyLost() would be called.

//比如丢失所有IPv4链路DNS服务器(或者丢失仅有的Ipv6默认网关)会构成Ipv4(Ipv6)配置丢失。

Callback#notifyLost()函数将会被调用。

- Since it can be non-trivial to reacquire certain IP provisioning state it may be best for the link to disconnect completely and reconnect afresh.

//因为重新获取特定的IP配置状态是非常重要的。最好的方式是完全断开连接,然后进行重连。


IpReachabilityMonitor实现

1、在给定的LinkProperties实例中找到关注的链路neighbours网络,这些neighbours网络被通过updateLinkProperties()方法添加到一个"watch list"中。这通常意味着所有默认网关和链路DNS服务器。

public void updateLinkProperties(LinkProperties lp)

      mLinkProperties = new LinkProperties(lp);           

     MapnewIpWatchList = new HashMap<>();           

     final Listroutes = mLinkProperties.getRoutes();

  //将处于链接状态的默认网关加入IpWatchList

    for (RouteInfo route : routes) {

            if (route.hasGateway()) {

                       InetAddress gw = route.getGateway();

                            if (isOnLink(routes, gw)) {

                                            newIpWatchList.put(gw, getNeighborStateLocked(gw);}} }

//将处于链接状态的DNS服务器加入IpWatchList

for (InetAddress nameserver : lp.getDnsServers()) {

           if (isOnLink(routes, nameserver)) {

                    newIpWatchList.put(nameserver, getNeighborStateLocked(nameserver));}}

mIpWatchList = newIpWatchList;

mIpWatchListVersion++;

2、我们连续不断地监听netlink消息(RTM_NEWNEIGH,RTM_DELNEIGH),仅仅关注在watch list中的neighbours网络。

public IpReachabilityMonitor(Context context, String ifName, Callback callback,

AvoidBadWifiTracker tracker) throws IllegalArgumentException {

...

//NetlinkSocketObserver类是用来建立Socket连接,不断地接受来自kernel的netlink消息,并并解析消息,判读是否加入IpWatchList中。具体信息可以参考下面NetlinkSocketObserver部分解析

mNetlinkSocketObserver = new NetlinkSocketObserver();

mObserverThread = new Thread(mNetlinkSocketObserver);

mObserverThread.start();

...

}


3、可以通过调用probeAll()方法来强制执行probed所有在watch list中的链路neighbours网络。不管什么时候,对于想验证链路上关键neighbour网络依然可达,这个方法是重要的,应该被调用,比如当在不同的BSSID之间Roaming的时候。

//将IpWatchList中Key集合取出来,然后通过调用probeNeighbor(int ifIndex, InetAddress ip)验证相应的neighbour是否可达。

public void probeAll() {

//将IpWatchList中Key集合赋值给ipProbeList

Set<InetAddress>  ipProbeList = new HashSet();

ipProbeList.addAll(mIpWatchList.keySet());

for (InetAddress target : ipProbeList) {

final int returnValue = probeNeighbor(mInterfaceIndex, target);

/// M: Add UDP packet for ARP retry

//下面是MTK添加的策略,针对probeNeighbor失败的情况下,使用UDP重新触发一次ARP请求

if (returnValue != 0) {

// Use UDP broadcast to trigger ARP procedure.

sendUdpBroadcast(target);}}}

private static int probeNeighbor(int ifIndex, InetAddress ip):

对于具体接口Index号上的给定Ip地址,使内核Kernel执行neighbour网络可达性检测(IPv4 ARP或 IPv6 ND)。

如果网络可达性检测请求成功传输到内核kernel,返回0; 其他返回一个非0的错误码。

private static int probeNeighbor(int ifIndex, InetAddress ip) {

...

//将ip地址和相应的接口号ifIndex封装到PROBE消息中

final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(

1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);

//创建NetlinkSocket,成功后进行连接kernel, 然后发送消息;

最后接收消息,并通过Netlink对消息进行解析。

try (NetlinkSocket nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE)) {

nlSocket.connectToKernel();

nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);

final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);

// recvMessage() guaranteed to not return null if it did not throw.

final NetlinkMessage response = NetlinkMessage.parse(bytes);

。。。//后面依据不同的response 返回不同的值。

}}


4、如果处于NUD_FAILED状态的neighbour网络的丢失将会导致Ipv4 或者Ipv6配置变得不完整(配置丢失),我们将会调用提供的Callback#notifyLost()函数。

private void handleNeighborLost(String msg) {

        LinkProperties whatIfLp = new LinkProperties(mLinkProperties);

        for (Map.Entryentry : mIpWatchList.entrySet()) {

                     //遍历IpWatchList中的所有neighbor,寻找value状态为NUD_FAILED的

                     if (entry.getValue() != StructNdMsg.NUD_FAILED) {

                           continue;

                      }

                  //从IpWatchList找到了value状态为NUD_FAILED的neighbor, 获取其IP地址;

                  //遍历mLinkProperties中路由Routes的网关是否有跟该IP地址一致的,如果有,则从当前的whatIfLp 中移除相应的路由Routes。

                  ip = entry.getKey();

                  for (RouteInfo route : mLinkProperties.getRoutes()) {

                  if (ip.equals(route.getGateway())) {

                          whatIfLp.removeRoute(route);

                 }

              //如果该IP地址不是IPv6 地址或者使能了避免badLink连接的性能,则从whatIfLp中移除Ip地址对应的DNS服务器。

                if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {

                           // We should do this unconditionally, but alas we cannot: b/31827713.

                           whatIfLp.removeDnsServer(ip);

                }

           }

        //调用LinkProperties的compareProvisioning函数得到whatIfLp与原始的mLinkProperties的区别。

          delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp);

          //如果delta值为ProvisioningChange.LOST_PROVISIONING,则回调函数notifyLost进行配置丢失的处理;该回调函数的实现是在IpManager中,详见IpManger中的分析。

          if (delta == ProvisioningChange.LOST_PROVISIONING) {

                    mCallback.notifyLost(ip, logMsg);

         }

}



Android - NetlinkSocketObserver


NetlinkSocketObserver简介

在IpReachabilityMonitor类中有一个子类NetlinkSocketObserver类。

在IpReachiabilityMonitor 构造函数中会创建NetlinkSocketObserver对象,并对该对象进行封装,创建一个Thread类 对象,并调用Thread类对象的Start方法,启动线程。

mNetlinkSocketObserver = new NetlinkSocketObserver();

mObserverThread = new Thread(mNetlinkSocketObserver);

mObserverThread.start();


NetlinkSocketObserver作用

NetlinkSocketObserver 类主要和Android Netlink 进行交互。

Android netlink :frameworks/base/services/net/java/android/net/netlink/...

该类的作用主要是建立NETLINK_ROUTE Socket,绑定相应的NetlinkSocketAddress。然后无限循环接收kernelReply信息并对该消息进行解析。


NetlinkSocketObserver实现

//通过实现Runnable接口创建线程,重写run方法;

该类的作用就是建立NETLINK_ROUTE Socket,绑定相应的NetlinkSocketAddress。然后无限循环接收kernelReply信息并对该消息进行解析。

关键接口实现:

Run()

  -->parseNetlinkMessageBuffer(byteBuffer, whenMs)

        -->evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);

private final class NetlinkSocketObserver implements Runnable

{

//建立NETLINK_ROUTE Socket,绑定相应的NetlinkSocketAddress。然后无限循环接收kernelReply信息并对该消息进行解析。

public void run() {

//1、建立NETLINK_ROUTE Socket,并绑定bind 相应的NetlinkSocketAddress

setupNetlinkSocket();

//2、接收Kernel的Reply信息

byteBuffer = recvKernelReply();

//3、对接收到的KernelReply信息进行解析

parseNetlinkMessageBuffer(byteBuffer, whenMs);

}

//将ByteBuffer解析为NetlinkMessage格式消息,并判断消息类型,调用评估消息是否更新相应ipadress的IpWatchList。

private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs)

/*

//1、通过调用NetlinkMessage类将ByteBuffer解析为NetlinkMessage格式消息

final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);

//2、判断解析出现的NetlinkMessage消息类型,确保是RtNetlinkNeighborMessage消息才继续进行评估;如果是NetlinkErrorMessage或不是RtNetlinkNeighborMessage消息直接退出。

nlMsg instanceof NetlinkErrorMessage

nlMsg instanceof RtNetlinkNeighborMessage 

//3、调用evaluateRtNetlinkNeighborMessage对RtNetlinkNeighborMessage消息进行评估。

evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);

*/

//依据从RtNetlinkNeighborMessage获取的msgType和nudState,对IpWatchList中相应的InetAddress的nudState 进行更新。

private void evaluateRtNetlinkNeighborMessage

(RtNetlinkNeighborMessage neighMsg, long whenMs)

/*

//1、从neighMsg取目的IP地址,并判断该IP地址是否在IpWatchList中,不在则直接退出函数返回

final InetAddress destination = neighMsg.getDestination();

//2、从neighMsg取msgType和nudState,并根据他们进行IpWatchList 的更新

final short msgType = neighMsg.getHeader().nlmsg_type;

final short nudState = ndMsg.ndm_state;

final short value =

(msgType == NetlinkConstants.RTM_DELNEIGH) ? StructNdMsg.NUD_NONE: nudState;

mIpWatchList.put(destination, value);

//3、如果nudState为NUD_FAILED状态,则执行handleNeighborLost动作

if (nudState == StructNdMsg.NUD_FAILED) {

handleNeighborLost(eventMsg);  //这个详见IpReachiabilityMonitor的实现方式的第4部分。

*/

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容