前端实现 Pcap 解析的实践方案

研究这个事情的起因是产品的一个需求,要求对远程的接口抓包进行实时展示。一般做法都是通过 tcpdump 进行抓包,然后使用 Wireshark 进行可视化展示,但是产品要求是要“实时”,也就不能通过生成 pcap 文件下载让用户手动打开 Wireshark 的方式了。

1. 技术选型

接到这个需求后,我们就开始了技术选型。主要就两个方向,一个是前端做数据展示,另一个是通过调用 Wireshark 做数据展示。

前端实现的话,单单靠 JavaScript 是没法达到要求的,首先没有现成的 pcap 解析库,从头开始造轮子太耗时间,而且 JavaScript 的性能瓶颈在那,首先就排除了 JavaScript 实现的可能。

虽然 JavaScript 可能达不到性能要求,但是现代浏览器已经支持 WebAssembly(简称 WASM),可以使用别的语言实现最消耗性能的部分。经过一番搜索,找到了一个开源的工具库:@goodtools/wiregasm,一个将 Wireshark 的包分析器编译成 WASM 的库,NICE!yes

如果是调用 Wireshark 做展示的话,则完全是另一套逻辑,是与操作系统强相关的路线。

使用过 迅雷 的童鞋一定对 thunder: 协议不会陌生,在你安装了迅雷的情况下,直接点击页面的 thunder: 连接,浏览器就会弹出打开迅雷的提示,点击打开后,迅雷软件会自动打开并开始下载点击的链接。包括现在的很多网盘,都采用了相同的方式从浏览器端唤醒客户端。

虽然在此之前我没做过相关需求,但是这个功能实现我一直记得,也是让我知道了从页面唤起应用的可能。

2. 技术实现

前端做可视化的话,就按照库作者提供的示例进行使用就行,中途也没什么大问题,性能也很强,测试打开 14w 个包的 pcap 文件除了解析耗时久一点,解析完成后流畅度非常好。这里有我提供的示例项目。

虽然前端实现看起来很棒,但是缺点也很明显:解析文件完全放在内存中,解析的文件越大,内存占用越高;其次是作者只提供了一次性 load 数据的接口,并不支持动态 add 的接口,这就导致无法直接实现产品要的 实时 展示。虽然也想过通过浏览器提供的 indexedDB 来间接实现 实时解析,但这个复杂度过高,在有更好的方案前作为备选方案。

如果是通过调用 Wireshark 客户端做数据可视化,那么就简单很多,我们只需要参照上面提到的唤起迅雷的方案,实现自定义协议,通过自定义协议调用我们写的程序做数据包获取处理,再将处理后的数据 “转发” 给 Wireshark 即可。因为之前做过调研,Wireshark 支持管道输入,所以技术上没有障碍。

2.1 数据获取程序

获取 pcap 数据本质上就是从远程接收二进制数据,能支持传输二进制数据的方式主要是基于 HTTP 的文件下载,另一个就是 websocket 的二进制消息。

文件下载因为文件流不知道什么时候会结束,会产生内存积压从而导致程序崩溃,而 websocket 是基于消息,不会有这个问题,故 tcpdump 的结果传输就基于 websocket 传输。

代码实现上,虽然我们很熟悉 nodejs,但是考虑到目前 nodejs 构建出的可执行程序的体积,我们还是换用了其它编译语言:golang,GUI 框架使用了 lxn/walk。

2.2 自定义协议实现

浏览器能处理的自定义协议完全就跟操作系统强相关了,通过查询学习,这里分别列出 Windows 和 MacOS 下的注册方式。

2.2.1 Windows

Windows 下是通过添加注册表的方式进行注册,下面是注册表内容,需根据实际情况做修改。

Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\custom.protocol]
@="URL:custom.protocol"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\custom.protocol\DefaultIcon]
@="C:\\WINDOWS\\system32\\cmd.exe,1"
[HKEY_CLASSES_ROOT\calc\shell]
[HKEY_CLASSES_ROOT\calc\shell\open]
[HKEY_CLASSES_ROOT\calc\shell\open\command]
@="\"C:\\WINDOWS\\system32\\calc.exe\" \"%1\""

将上面内容另存为 reg 文件并执行后,我们就在系统中注册了“custom.protocol:”协议了。

2.2.2 MacOS

MacOS 下是通过修改自定义程序的"Contents/Info.plist"文件来实现,在最外层的 Dict 下的任意位置,添加如下内容即可。

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>CalcURLHandler</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>calc</string>
    </array>
  </dict>
</array>

注意,如果是修改已有的程序,程勋签名会失效,需要重新签名并打开一次程序才能注册自定义协议。

3. 安装包构建

目前只打包了 Windows 下的安装包,使用的打包工具是 Inno Setup,程序自带向导,构建一个简单的安装包还是很容易的。

因为我们程序依赖 Wireshark,所以最好是在安装包中自带 Wireshark,如果用户环境没有安装 Wireshark,那我们可以提示用户安装 Wireshark,这部分 iss 代码如下:

[Files]
Source: "files\{#WiresharkInstaller}"; DestDir: "{tmp}"; Flags: dontcopy

[Tasks]
Name: installWireshark; Description: "Install Wireshark"; Check: ShouldInstallWireshark

[Code]
const
  ERROR_SUCCESS = 0;

function ShouldInstallWireshark: Boolean;
var
  InstallPath: string;
begin
  Result := not RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Wireshark.exe', '', InstallPath);

  if Result then
    Result := not RegKeyExists(HKEY_CURRENT_USER, 'Software\Wireshark');
end;

procedure CurStepChanged(CurStep: TSetupStep);
var
  ResultCode: Integer;
begin
  if CurStep = ssPostInstall then
  begin
    if WizardIsTaskSelected('installWireshark') then
    begin
      ExtractTemporaryFile('{#WiresharkInstaller}');
      Exec(ExpandConstant('{tmp}\{#WiresharkInstaller}'), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
    end;
  end;
end;

4. 总结

整个功能下来,复杂度感觉没有多少,完全就是看研发人员是否对涉及到的东西是否有一定的了解,知识点很杂,考验的就是一个知识广度。