【问题标题】:Calculate bandwidth usage per IP with scapy, iftop-style使用 scapy,iftop 样式计算每个 IP 的带宽使用情况
【发布时间】:2014-02-01 10:13:11
【问题描述】:

我正在使用 scapy 嗅探镜像端口并生成前 10 名“谈话者”的列表,即在我的网络上使用最多带宽的主机列表。我知道iftopntop 等工具已经可用,但我需要对输出进行更多控制。

以下脚本对 30 秒的流量进行采样,然后以“源主机 -> 目标主机:字节”的格式打印前 10 名通话者的列表。太好了,但是如何计算平均每秒字节数

我感觉将 sample_interval 更改为 1 秒并不能对流量进行良好的采样,因此我似乎需要对其进行平均。所以我在脚本末尾尝试了这个:

每秒字节数 =(总字节数 / sample_interval)

但生成的 Bytes/s 似乎要低得多。例如,我以 1.5 MB/s 的节流速率在两台主机之间生成了 rsync,但使用上述平均计算,我的脚本继续计算这些主机之间的速率约为 200 KB/s...远低于 1.5 MB /s 如我所料。我可以通过 iftop 确认 1.5 MB/s 实际上是这两个主机之间的速率。

我是否使用 scapy 错误地汇总了数据包长度(请参阅 traffic_monitor_callbak 函数)?或者这完全是一个糟糕的解决方案:)?

from scapy.all import *
from collections import defaultdict
import socket
from pprint import pprint
from operator import itemgetter

sample_interval = 30  # how long to capture traffic, in seconds

# initialize traffic dict
traffic = defaultdict(list)

# return human readable units given bytes
def human(num):
    for x in ['bytes','KB','MB','GB','TB']:
        if num < 1024.0:
            return "%3.1f %s" % (num, x)
        num /= 1024.0

# callback function to process each packet
# get total packets for each source->destination combo
def traffic_monitor_callbak(pkt):
    if IP in pkt:
        src = pkt.sprintf("%IP.src%")
        dst = pkt.sprintf("%IP.dst%")

        size = pkt.sprintf("%IP.len%")

        # initialize
        if (src, dst) not in traffic:
            traffic[(src, dst)] = 0

        else:
            traffic[(src, dst)] += int(size)

sniff(iface="eth1", prn=traffic_monitor_callbak, store=0, timeout=sample_interval)

# sort by total bytes, descending
traffic_sorted = sorted(traffic.iteritems(), key=itemgetter(1), reverse=True)    

# print top 10 talkers
for x in range(0, 10):
    src = traffic_sorted[x][0][0]
    dst = traffic_sorted[x][0][1]
    host_total = traffic_sorted[x][3]

    # get hostname from IP
    try:
        src_hostname = socket.gethostbyaddr(src)
    except:
        src_hostname = src

    try:    
        dst_hostname = socket.gethostbyaddr(dst)
    except:
        dst_hostname = dst


    print "%s: %s (%s) -> %s (%s)" % (human(host_total), src_hostname[0], src, dst_hostname[0], dst)

我不确定这是一个编程(scapy/python)问题还是更多的一般网络问题,所以我称之为网络编程问题。

【问题讨论】:

    标签: python networking network-programming scapy


    【解决方案1】:

    这可能不是,但您是否可能混淆了每秒兆*比特* (Mb/s) 和每秒兆*字节* (MB/s )?您似乎正在测量以字节为单位发送的数据量,然后将其转换为 MB/s,但我想知道您是否将 uo ws 指定为 1.5 Mb/秒。如果是这样,您的脚本为您提供 200 kB/s 的事实至少在 1.5 Mb/s 的正确范围内......

    【讨论】:

    • 我在 rsync 中使用--bwlimit=1500 选项。 man rsync 说这是给limit I/O bandwidth; KBytes per second,所以大约是 1.5 兆字节/秒。我可以通过 iftop 和 (Cacti)[cacti.net/] 图表确认我确实在推动如此多的流量。感谢您的澄清。
    【解决方案2】:

    你好,

    首先,您发布的代码中有一个错误:您可能指的是host_total = traffic_sorted[x][1],而不是host_total = traffic_sorted[x][3]

    然后,您有一个错误:您忘记将host_total 除以sample_interval 值。

    由于您还想添加接收者到发送者的流量和发送者到接收者,我认为最好的方法是使用“有序”元组(顺序本身在这里并不重要,字典顺序可能是很好,但您也可以使用算术顺序,因为 IP 地址是 4 个八位字节整数)作为 Counter 对象的键。这似乎工作得很好:

    #! /usr/bin/env python
    
    sample_interval = 10
    interface="eth1"
    
    from scapy.all import *
    from collections import Counter
    
    
    # Counter is a *much* better option for what you're doing here. See
    # http://docs.python.org/2/library/collections.html#collections.Counter
    traffic = Counter()
    # You should probably use a cache for your IP resolutions
    hosts = {}
    
    def human(num):
        for x in ['', 'k', 'M', 'G', 'T']:
            if num < 1024.: return "%3.1f %sB" % (num, x)
            num /= 1024.
        # just in case!
        return  "%3.1f PB" % (num)
    
    def traffic_monitor_callback(pkt):
        if IP in pkt:
            pkt = pkt[IP]
            # You don't want to use sprintf here, particularly as you're
            # converting .len after that!
            # Here is the first place where you're happy to use a Counter!
            # We use a tuple(sorted()) because a tuple is hashable (so it
            # can be used as a key in a Counter) and we want to sort the
            # addresses to count mix sender-to-receiver traffic together
            # with receiver-to-sender
            traffic.update({tuple(sorted(map(atol, (pkt.src, pkt.dst)))): pkt.len})
    
    sniff(iface=interface, prn=traffic_monitor_callback, store=False,
          timeout=sample_interval)
    
    # ... and now comes the second place where you're happy to use a
    # Counter!
    # Plus you can use value unpacking in your for statement.
    for (h1, h2), total in traffic.most_common(10):
        # Let's factor out some code here
        h1, h2 = map(ltoa, (h1, h2))
        for host in (h1, h2):
            if host not in hosts:
                try:
                    rhost = socket.gethostbyaddr(host)
                    hosts[host] = rhost[0]
                except:
                    hosts[host] = None
        # Get a nice output
        h1 = "%s (%s)" % (hosts[h1], h1) if hosts[h1] is not None else h1
        h2 = "%s (%s)" % (hosts[h2], h2) if hosts[h2] is not None else h2
        print "%s/s: %s - %s" % (human(float(total)/sample_interval), h1, h2)
    

    Scapy 可能不够快,无法完成这项工作。可以肯定的是,你可以,例如tcpdump -w,将你的流量捕获到一个文件sample_interval秒,然后运行(顺便看看将函数应用于数据包的方式,我认为知道你是否使用Scapy是一件好事经常):

    #! /usr/bin/env python
    
    sample_interval = 10
    filename="capture.cap"
    
    from scapy.all import *
    from collections import Counter
    
    traffic = Counter()
    hosts = {}
    
    def human(num):
        for x in ['', 'k', 'M', 'G', 'T']:
            if num < 1024.: return "%3.1f %sB" % (num, x)
            num /= 1024.
        return  "%3.1f PB" % (num)
    
    def traffic_monitor_callback(pkt):
        if IP in pkt:
            pkt = pkt[IP]
            traffic.update({tuple(sorted(map(atol, (pkt.src, pkt.dst)))): pkt.len})
    
    # A trick I like: don't use rdpcap() that would waste your memory;
    # iterate over a PcapReader object instead.
    for p in PcapReader("capture.cap"):
        traffic_monitor_callback(p)
    
    for (h1, h2), total in traffic.most_common(10):
        h1, h2 = map(ltoa, (h1, h2))
        for host in (h1, h2):
            if host not in hosts:
                try:
                    rhost = socket.gethostbyaddr(host)
                    hosts[host] = rhost[0]
                except:
                    hosts[host] = None
        h1 = "%s (%s)" % (hosts[h1], h1) if hosts[h1] is not None else h1
        h2 = "%s (%s)" % (hosts[h2], h2) if hosts[h2] is not None else h2
        print "%s/s: %s - %s" % (human(float(total)/sample_interval), h1, h2)
    

    【讨论】:

    • 是的,我相信您对发送方和接收方流量的看法是正确的。在查看iftop 的输出时,我可以看到它显示了两个主机之间的组合流量。什么是最后组合结果的好pythonic方式,即组合元组?例如,如果您在 (host1,host2) + (host2, host1) 的 traffic dict 中有结果。
    • 刚刚更新了我的答案。这是否完全回答了您的问题?
    • 哇,好代码。比我的更有效率和聪明:)。我仍然很好奇为什么在与iftop 并排比较时,即使将流量方向结合起来,每个主机对的最终总数仍然如此之低? iftop 根据我的节流 rsync 测试似乎是正确的。也许 sample_interval 应该设置得更高,比如 60 秒?我想知道iftop 是如何在幕后做事的……这可能是最好的例子,因为我几乎在尝试构建相同的东西,但使用的是scapy
    • 该死!所以这仍然不是你问题的好答案!我已经更新了我的答案,以检查 Scapy 是否太慢并丢失数据包。
    • 是的,看起来更准确!总数最终略高,但看起来要好得多。有没有办法让 scapy 查看最后 X 秒的时间戳?即我将 scapy 指向一个不断更新的 tcpdump cap 文件,scapy 可以从文件中获取最后 X 秒。除非您愿意,否则您不必为此花时间:)。另外,您可以编辑您的答案以仅显示最终代码和说明“scapy 可能太慢”的解释吗?然后我会标记为已接受。这对其他人会很有帮助。感谢您的所有努力。
    猜你喜欢
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    相关资源
    最近更新 更多