python-mtrpacket 是使用 Python 实现的异步网络探测工具。其当前版本(1.0.0-3)在 Arch Linux 主线的 x86_64 架构能正常编译通过,在 riscv64 下报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.E/bin/sh: line 1: mtr-packet-missing: command not found
..
======================================================================
ERROR: test_commands (__main__.TestCommands)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/build/python-mtrpacket/src/mtr-packet-python-1.0.0/test/test.py", line 210, in test_commands
asyncio_run(self.async_commands())
File "/build/python-mtrpacket/src/mtr-packet-python-1.0.0/test/test.py", line 218, in asyncio_run
return loop.run_until_complete(loop.create_task(coro))
File "/usr/lib/python3.10/asyncio/base_events.py", line 641, in run_until_complete
return future.result()
File "/build/python-mtrpacket/src/mtr-packet-python-1.0.0/test/test.py", line 203, in async_commands
async with mtrpacket.MtrPacket() as mtr:
File "/build/python-mtrpacket/src/mtr-packet-python-1.0.0/mtrpacket/__init__.py", line 138, in __aenter__
await self.open()
File "/build/python-mtrpacket/src/mtr-packet-python-1.0.0/mtrpacket/__init__.py", line 314, in open
if not await self.check_support('send-probe'):
File "/build/python-mtrpacket/src/mtr-packet-python-1.0.0/mtrpacket/__init__.py", line 368, in check_support
(_, args) = await self._command('check-support', check_args)
File "/build/python-mtrpacket/src/mtr-packet-python-1.0.0/mtrpacket/__init__.py", line 275, in _command
return await future
mtrpacket.ProcessError: failure to communicate with subprocess "./nc_mock.sh" (is it installed and in the PATH?)

----------------------------------------------------------------------
Ran 4 tests in 1.296s

FAILED (errors=1)

这里面 /bin/sh: line 1: mtr-packet-missing: command not found 是正常输出,属于第三个测试点,不用管它。研究下代码就能发现这个错误是预期行为,会被 self.assertRaises 抓到。E(错误)的是第二个测试点,本不应该出现 mtrpacket.ProcessError,但是出现了。

看一下 test/nc_mock.sh,发现很简单,就这么一点东西:

1
2
3
#!/bin/sh

nc localhost 8901

那这怎么就在 riscv64 上锅了呢?

首先怀疑 qemu-userargv[0] 导致找不到 nc_mock.sh,但这很容易排除。只需要在 nc_mock.sh 中加入:

1
sleep 30

然后发现测试在第二个点上卡住三十秒,就知道其实 nc_mock.sh 是被运行了的。由此可以确定:原本的报错提示具有误导性。

那么,既然运行了,怎么就报错了呢?修改一下 PKGBUILD(Arch Linux 编译包时使用的脚本),把 check() 中的 ./test.sh || bash -i,这样我们就在 test.sh(它会间接调用 nc_mock.sh)非零返回的时候得到进入到打包所用的 rootfs 环境的快捷通道。

之后通过修改 test/test.pymtrpacket/__init__.py,最终定位到出问题的地方:

首先是测试入口,在 test.py 里面创建了一个 mtr 的服务端,之后去启动对应的客户端。第二个测试点使用 nc_mock.sh 作为客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mtr_packet_executable = os.environ.get('MTR_PACKET')  # ./nc_mock.sh
if not mtr_packet_executable:
mtr_packet_executable = 'mtr-packet'

self._subprocess_name = mtr_packet_executable
self.process = await asyncio.create_subprocess_shell(
mtr_packet_executable,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE)

self._result_task = asyncio.ensure_future(self._dispatch_results())
self._opened = True

if not await self.check_support('send-probe'):
await self.close()

这里可以看到,创建了一个 ./nc_mock.sh 的子进程。这个子进程会通过 nc 连接到 localhost8901 端口,在这个端口上监听的是 test.py 预先创建好的服务端,于是就可以走正常测试流程了。

之后是测试流程,可以看到立马执行的是 check_support('send-probe'),追进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
async def check_support(self, feature: str) -> bool:
check_args = {'feature': feature}
(_, args) = await self._command('check-support', check_args)

async def _command(self, command_type: str, arguments: Dict[str, str]) -> Tuple[str, Dict[str, str]]:
# ...
command_str = token + ' ' + command_type
for argument_name in arguments:
argument_value = arguments[argument_name]
command_str += ' ' + argument_name + ' ' + argument_value
command_str += '\n'

self.process.stdin.write(command_str.encode('ascii'))

到这里,就出问题了。由于 python-mtrpacketcheckdepends 依赖是 gnu-netcat,这个 netcat 在 riscv64 上的表现和 x86_64 上不一致,会提早退出。如果将 LOCALE 设置为 itsk,还能看到对应语言(意大利语/斯洛伐克语)的错误提示。为什么只有这两种语言呢?我也不知道,反正 Package Contents 里只有这两个:

1
2
3
4
5
6
usr/share/locale/it/
usr/share/locale/it/LC_MESSAGES/
usr/share/locale/it/LC_MESSAGES/netcat.mo
usr/share/locale/sk/
usr/share/locale/sk/LC_MESSAGES/
usr/share/locale/sk/LC_MESSAGES/netcat.mo

So……反正我也不关心具体报错信息了,意大利语我也看不懂。直接把 PKGBUILD 里的 checkdependsgnu-netcat 换成 openbsd-netcat 完事,反正 ./nc_mock.sh 里的简单用法还不至于触及到两个 variant 存在不兼容的黑色高级部分。

来源:https://blog.jiejiss.com/