memcpy后数据不对_通过 JNI 移植一个 tracepath 追踪路由数据链给你的应用

本文介绍了将Linux的tracepath指令移植到Android的过程,通过JNI将tracepath封装为Java接口,使其支持IPV4和IPV6。在移植过程中,主要涉及接口约定、源码改造以适应Android环境,以及结果回调到Java层的实现。项目已开源,方便开发者直接使用或参考移植其他网络诊断工具。
摘要由CSDN通过智能技术生成

背景

Linux 的 tracepath 指令可以追踪数据到达目标主机的路由信息,同时还能够发现 MTU 值。它跟踪路径到目的地,沿着这条路径发现 MTU。它使用 UDP 端口或一些随机端口。它类似于 Traceroute,只是不需要超级用户权限,并且没有花哨的选项。

Android 也是移植的它,其源码放置位置在platform/external/iputils/tracepath6.c。我们之所以直接移植tracepath6.c而不是tracepath.c的原因是 tracepath6 支持 IPV6 和 IPV4 两种模式,而tracepath.c仅仅支持 IPV4,所以一把梭后我们直接完美兼容了两种。

最近刚好在调研网络诊断覆盖能力,所以顺手移植了下它,大致效果如下。

53ba5ef4dfe7880a98dc6204f35ee39b.png
demo 效果

移植后开箱即用地址https://github.com/yanbober/android-tracepath,喜欢就给个小星星呗,一闪一闪亮晶晶。

开始移植

本想捡个现成,去看了platform/external/iputils/tracepath6.c源码发现这货直接是写死默认假定 IPV6 模式的,需要不同模式的话需要自己执行命令时传递模式参数,不支持自己动态识别模式,所以需要移植改造。

定义 Java 接口约定

为了方便给 app 使用,需要通过 JNI 包装到 Java 层接口,约定如下:

package cn.yan.android.tracepath;

public final class AndroidTracePath {
    static {
        System.loadLibrary("tracepath-compat");
    }

    private StateListener mStateListener;

    public AndroidTracePath(StateListener stateListener) {
        this.mStateListener = stateListener;
    }
 //业务方调用开始 tracepath 的方法,hostName 是你的域名或者 ip
    public void startTrace(String hostName) {
        nativeInit();
        nativeStartTrace(hostName);
    }

    public native void nativeInit();

    public native void nativeStartTrace(String hostName);

    public void nativeOnStart() {
        if (null != mStateListener) {
            mStateListener.onStart();
        }
    }

    public void nativeOnUpdate(String update) {
        if (null != mStateListener) {
            mStateListener.onUpdate(update);
        }
    }

    public void nativeOnEnd() {
        if (null != mStateListener) {
            mStateListener.onEnd();
        }
    }
 //tracepath 回调状态
    public interface StateListener {
        void onStart();
        void onUpdate(String update);
        void onEnd();
    }
}

接着就是对应 JNI 层的接口了,这里没啥说的,都是老套路,一键生成也罢,动态映射也罢,随意摆弄,反正最终能调用到tracepath6.c源码就行。

我们重点是改造tracepath6.c,由于这玩意默认编译后是一个可执行文件,我们通过 cmake 需要当作依赖编译,所以他的 main 方法入口就不再适合我们了,我们需要进行改造(换个方法名即可),如下:

//int main(int argc, char **argv) 替换为 tracepath 函数
int tracepath(int argc, char **argv)

这玩意需要给我们的 JNI 包装接口(apicompat.c)调用,所以我们给他新建一个头文件把这个方法报漏一下,如下:

//tracepath6.h

#ifndef ANDROIDTRACEPATH_TRACEPATH6_H
#define ANDROIDTRACEPATH_TRACEPATH6_H

int tracepath(int argc, char** arg);

#endif //ANDROIDTRACEPATH_TRACEPATH6_H

手机连着 ipv4 的网络一运行,卧槽,跪了,定位代码发现tracepath6.c里面是写死模式的,需要动态适配,改造点如下:

......
sa_family_t family = AF_UNSPEC; //把这里初值AF_INET6换成AF_UNSPEC
......
int tracepath(int argc, char **argv){
 ......
 memset(&hints, 0, sizeof(hints));
 hints.ai_family = AF_UNSPEC; //把这里family变量换成AF_UNSPEC
 hints.ai_socktype = SOCK_DGRAM;
 hints.ai_protocol = IPPROTO_UDP;
#ifdef USE_IDN
 hints.ai_flags = AI_IDN;
#endif
 gai = getaddrinfo(argv[0], pbuf, &hints, &ai0);
 if (gai) {
  fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai));
        return 1;
 }

 fd = -1;
 for (ai = ai0; ai; ai = ai->ai_next) {
  //这里一段判断family的逻辑删掉
  if (ai->ai_family != AF_INET6 &&
      ai->ai_family != AF_INET)
   continue;
  family = ai->ai_family;
  fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
  if (fd 0)
   continue;
  memcpy(&target, ai->ai_addr, sizeof(target));
  targetlen = ai->ai_addrlen;
  break;
 }
 ......
}

可以看到,贯穿全流程的模式是通过一个全局的 family 变量类型来维护的,默认改为 AF_UNSPEC 后就会自动探测类型,匹配到 AF_INET6 或者 AF_INET 则走自己对应逻辑,这样就能完美兼容 IPV6 和 IPV4 了。

到此运行能出结果了,但是 tracepath 的打印结果没法输出到 Java 层回调中,我们需要继续改造。常规想法就是一个一个换掉tracepath6.c里面的 printf 函数为 JNI 回调 Java 方法实现,这样比较麻烦。我们还是采用了一把梭的模式,如下:

//tracepath6.c

#include "./../apicompat.h"

#define printf(...) callbackOnUpdate(__VA_ARGS__)

如上通过一个宏直接替换 printf 为我们 JNI 接口层的 callbackOnUpdate 函数,这个函数的作用就是调用 Java 方法,这样就能把数据传递回去了。

到此基本 ok 了,还差最后的优化,你也看到了,tracepath6.c编写初衷是一个可执行程序,现在把它移植成 so,所以我们不能在直接 exit 了,相关地方都需要一把梭的替换为 return 解决问题,到此完美解决所有。

用起来

项目地址是https://github.com/yanbober/android-tracepath,里面也有详细的 maven 仓库用法,直接依赖即可开箱使用。如果你需要移植 iputils 下面其他能力,则可以照猫画虎,如果遇到难处,不妨扫码加我微信联系技术支持。

到此结题,扫描右侧微信加个好友呗,朋友圈更精彩。

35a4b8d97ea007393ec42ec64179b1d6.png

小编 朋友圈技术更精彩>

b11b72184bb7fa0dce3ea16df690af0f.gif▼往期精彩回顾▼你需要知道的神器之 editorconfigJetpack 全家桶之 App Startup 看完源码后真不是你们说的那样

c5522558591c8bac8128025148476297.png点击左下角阅读原文查看历史经典技术问题汇总,看完顺手一键三连呀~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值