0x00 前景提要

在搬砖过程中遇到一个巨坑,有一个python模块实现了一个本地监听多个端口的功能,为了防止监听端口冲突端口使用了python中的random模块中的randint函数来实现随机端口的生成。然而两次调用后却发现一直报error: [Errno 98] Address already in use,明显就是端口被占用。这个就比较奇怪了,因为该模块一次监听5个端口。怎么可能5个端口都会有冲突。

0x01 排查

首先想到的就是random是否为伪随机,在查阅了以后发现

Warning The pseudo-random generators of this module should not be used for security purposes. Use os.urandom() or SystemRandom if you require a cryptographically secure pseudo-random number generator.

确实是伪随机,但是默认随机数生成种子是从/dev/urandom或者是系统时间戳获取的。两次调用也不会是同时调用的,所以种子肯定不会是一样的。

后来经大神提醒有了点眉目,因为两个进程的父进程是同一个进程。进程在导入random模块的时候种子已经选好了。所以不同子进程生成的随机数序列肯定是一样的。

下面写个python验证下

import random
import os
if os.fork() == 0:
print "[rand 1] %d" % random.randint(1,100)
print "[rand 1] %d" % random.randint(1,100)
print "[rand 1] %d" % random.randint(1,100)
else:
print "[rand 2] %d" % random.randint(1,100)
print "[rand 2] %d" % random.randint(1,100)
print "[rand 2] %d" % random.randint(1,100)

通过执行结果可以发现,确实证实了猜测

~ » python test.py
[rand 2] 51
[rand 2] 58
[rand 2] 31
[rand 1] 51
[rand 1] 58
[rand 1] 31

0x02 解决

知道了原因,要解决就很简单了。只要在生成随即数的时候重置种子就可以了。

import random
import os
if os.fork() == 0:
random.seed()
print "[rand 1] %d" % random.randint(1,100)
print "[rand 1] %d" % random.randint(1,100)
print "[rand 1] %d" % random.randint(1,100)
else:
random.seed()
print "[rand 2] %d" % random.randint(1,100)
print "[rand 2] %d" % random.randint(1,100)
print "[rand 2] %d" % random.randint(1,100)

结果也证实了这种解决方案可行

~ » python test.py
[rand 2] 7
[rand 2] 52
[rand 2] 43
[rand 1] 16
[rand 1] 70
[rand 1] 97