Python3调用fping来进行icmp扫描

last modified : 2015-03-11 | published: 2015-04-14 | category:

为什么要用fping

1. 纯python的python3-ping需要改造。

http://stackoverflow.com/questions/8888880/python-icmp-ping-implementation-when-pinging-multiple-ips-from-threads

This is happening because of the nature of ICMP. ICMP has no concept of ports, so all ICMP messages are received by all listeners.

The usual way to disambiguate is to set a unique identifier in the ICMP ECHO REQUEST payload, and look for it in the response. This code appears to do that, but it uses the current process id to compose the ID. Since this is multithreaded code, they will share a process id and all listeners in the current process will think all ECHO REPLYs are ones they themselves sent!

这是因为ICMP的性质不同于TCP/UDP。 ICMP没有端口的概念,所有ICMP包被所有监听ICMP的程序接收。python3-ping使用进程id组成标识符,多线程将共享一个进程ID。 处理这个问题的的常用方法是ICMP ECHO REQUEST字段设置一个唯一标识符,这个标识符同时由线程ID和进程ID组成,并在响应中处理。

2. 相比多线程的python-ping,使用fping,并使用正则处理返回内容,更省资源。

这个是我万万没想到的。但实际测试确实如此,fping扫描100个c段,欲在相同时间内完成,使用基于current.futures的多线程完成,fping要节省80%的内存。

和C比,多线程下,Python真是太耗内存了...

3.更安全

因为扫描程序会在服务器上跑在一个受限用户下,相比赋予python3权限使用raw socket的权限,只赋予一个fping,更满足公司的安全制度。

CODE

def ping_subnet(subnet):
    p = subprocess.Popen(['/usr/bin/fping', '-g', subnet.cidr(), '-c1', '-q'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, error = p.communicate(timeout=120)

    fping_regex = re.compile('(?P<ip_address>^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*(?P<xmt>\d{1,2})\/(?P<rcv>\d{1,2})\/(?P<loss>\d{1,3})%.*', re.DOTALL)
    fping_result = {}

    for line in error.decode().splitlines():
        try:
            fping_result[fping_regex.search(line).groupdict()["ip_address"]] = int(fping_regex.search(line).groupdict()["loss"])
        except:
            pass

    pprint.pprint(fping_result)
    with transaction.atomic():
        for host in subnet.hosts.all():
            host.ping_latest_time = timezone.datetime.now()
            if fping_result.get(host.ip_address, 1) == 0:
                host.ping_last_success_time = timezone.datetime.now()
            host.save()
        subnet.ping_latest_time = timezone.datetime.now()
        subnet.save()
    return subnet