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

商务办公
Linux 的 tracepath 指令可以追踪数据到达目标主机的路由信息,同时还能够发现 MTU 值。它跟踪路径到目的地,沿着这条路径发现 MTU。它使用 UDP 端口或一些随机端口。

[[356805]]

背景

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

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

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

demo 效果

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

开始移植

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

定义 Java 接口约定

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

  1. package cn.yan.android.tracepath; 
  2.  
  3. public final class AndroidTracePath { 
  4.     static { 
  5.         System.loadLibrary("tracepath-compat"); 
  6.     } 
  7.  
  8.     private StateListener mStateListener; 
  9.  
  10.     public AndroidTracePath(StateListener stateListener) { 
  11.         this.mStateListener = stateListener; 
  12.     } 
  13.  //业务方调用开始 tracepath 的方法,hostName 是你的域名或者 ip 
  14.     public void startTrace(String hostName) { 
  15.         nativeInit(); 
  16.         nativeStartTrace(hostName); 
  17.     } 
  18.  
  19.     public native void nativeInit(); 
  20.  
  21.     public native void nativeStartTrace(String hostName); 
  22.  
  23.     public void nativeOnStart() { 
  24.         if (null != mStateListener) { 
  25.             mStateListener.onStart(); 
  26.         } 
  27.     } 
  28.  
  29.     public void nativeOnUpdate(String update) { 
  30.         if (null != mStateListener) { 
  31.             mStateListener.onUpdate(update); 
  32.         } 
  33.     } 
  34.  
  35.     public void nativeOnEnd() { 
  36.         if (null != mStateListener) { 
  37.             mStateListener.onEnd(); 
  38.         } 
  39.     } 
  40.  //tracepath 回调状态 
  41.     public interface StateListener { 
  42.         void onStart(); 
  43.         void onUpdate(String update); 
  44.         void onEnd(); 
  45.     } 

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

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

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

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

  1. //tracepath6.h 
  2.  
  3. #ifndef ANDROIDTRACEPATH_TRACEPATH6_H 
  4. #define ANDROIDTRACEPATH_TRACEPATH6_H 
  5.  
  6. int tracepath(int argc, char** arg); 
  7.  
  8. #endif //ANDROIDTRACEPATH_TRACEPATH6_H 

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

  1. ...... 
  2. sa_family_t family = AF_UNSPEC; //把这里初值AF_INET6换成AF_UNSPEC 
  3. ...... 
  4. int tracepath(int argc, char **argv) 
  5.  ...... 
  6.  memset(&hints, 0, sizeof(hints)); 
  7.  hints.ai_family = AF_UNSPEC; //把这里family变量换成AF_UNSPEC 
  8.  hints.ai_socktype = SOCK_DGRAM; 
  9.  hints.ai_protocol = IPPROTO_UDP; 
  10. #ifdef USE_IDN 
  11.  hints.ai_flags = AI_IDN; 
  12. #endif 
  13.  gai = getaddrinfo(argv[0], pbuf, &hints, &ai0); 
  14.  if (gai) { 
  15.   fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai)); 
  16.         return 1; 
  17.  } 
  18.  
  19.  fd = -1; 
  20.  for (ai = ai0; ai; ai = ai->ai_next) { 
  21.   //这里一段判断family的逻辑删掉 
  22.   if (ai->ai_family != AF_INET6 && 
  23.       ai->ai_family != AF_INET) 
  24.    continue
  25.   family = ai->ai_family; 
  26.   fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 
  27.   if (fd < 0) 
  28.    continue
  29.   memcpy(&target, ai->ai_addr, sizeof(target)); 
  30.   targetlen = ai->ai_addrlen; 
  31.   break; 
  32.  } 
  33.  ...... 

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

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

  1. //tracepath6.c 
  2.  
  3. #include "./../apicompat.h" 
  4.  
  5. #define printf(...) callbackOnUpdate(__VA_ARGS__) 

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

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

 

责任编辑:武晓燕 来源: 码农每日一题
相关推荐

2021-04-14 15:59:50

博睿数据金融科技APM

2021-05-12 14:44:28

大数据数据链博睿数据

2018-08-26 05:38:44

路由器调制解调器网络设备

2020-09-06 22:59:35

Linux文件命令

2021-04-20 20:10:15

博睿数据数据链DNA

2023-01-30 16:21:24

Linux外观

2022-07-06 19:00:00

微服务框架链路

2018-08-22 17:32:45

2021-09-29 10:35:02

数据链DNA博睿数据

2022-12-22 08:22:17

Python图像图像处理

2018-01-30 05:04:02

2021-08-04 17:55:38

keysRedis数据库

2022-05-23 08:23:24

链路追踪SleuthSpring

2012-12-12 09:47:56

JavaScript

2012-03-05 19:43:00

lumia

2018-07-03 15:20:36

Promise函数借钱

2019-09-30 09:26:29

Java编程语言国旗

2021-05-12 14:48:12

大数据博睿数据数据链DNA

2020-09-15 13:56:08

公众号机器人图灵机器人

2009-06-22 13:50:00

java连接mysql
点赞
收藏

51CTO技术栈公众号