1

对于我为 OSX 构建的一个小工具,我想捕获从某个以太网控制器发送和接收的数据包的长度。

当我获取以太网卡时,我还会获得额外的信息,例如最大数据包大小、链接速度等。

当我启动(我称之为)“trafficMonitor”时,我会这样启动它:

static void initializeTrafficMonitor(const char* interfaceName, int packetSize) {
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t* sessionHandle = pcap_open_live(interfaceName, packetSize, 1, 100, errbuf);
    if (sessionHandle == NULL)
    {
        printf("Error opening session for device %s: %s\n", interfaceName, errbuf);
        return;
    }

    pcap_loop(sessionHandle, -1, packetReceived, NULL);
}

提供interfaceName的是接口的 BSD 名称,例如en0. 该packetSize变量是一个整数,我为该以太网适配器提供最大数据包大小(当时这似乎是合乎逻辑的)。例如,我的 WiFi 适配器的数据包大小是1538.

我的回调方法被调用packetReceived,看起来像这样:

void packetReceived(u_char* args, const struct pcap_pkthdr* header, const u_char* packet) {

    struct pcap_work_item* item = malloc(sizeof(struct pcap_pkthdr) + header->caplen);
    item->header = *header;
    memcpy(item->data, packet, header->caplen);
    threadpool_add(threadPool, handlePacket, item, 0);
}

我将数据包的所有属性填充到一个新结构中,并启动一个工作线程来分析数据包并处理结果。这是为了不让 pcap 等待,并尝试解决在添加此工作线程方法之前已经存在的问题。

handlePacket方法是这样的:

    void handlePacket(void* args) { 

        const struct pcap_work_item* workItem = args;
        const struct sniff_ethernet* ethernet = (struct sniff_ethernet*)(workItem->data);

        u_int size_ip;
        const struct sniff_ip* ip = (struct sniff_ip*)(workItem->data + SIZE_ETHERNET);
        size_ip = IP_HL(ip) * 4;
        if (size_ip < 20) {
            return;
        }
        const u_int16_t type = ether_packet(&workItem->header, workItem->data);
        switch (ntohs(type)) {
            case ETHERTYPE_IP: {

                char sourceIP[INET_ADDRSTRLEN];
                char destIP[INET_ADDRSTRLEN];

                inet_ntop(AF_INET, &ip->ip_src, sourceIP, sizeof(sourceIP));
                inet_ntop(AF_INET, &ip->ip_dst, destIP, sizeof(destIP));

                [refToSelf registerPacketTransferFromSource:sourceIP destinationIP:destIP packetLength:workItem->header.caplen packetType:ethernet->ether_type];

                break;
            }
            case ETHERTYPE_IPV6: {
                // handle v6
                char sourceIP[INET6_ADDRSTRLEN];
                char destIP[INET6_ADDRSTRLEN];

                inet_ntop(AF_INET6, &ip->ip_src, sourceIP, sizeof(sourceIP));
                inet_ntop(AF_INET6, &ip->ip_dst, destIP, sizeof(destIP));

                [refToSelf registerPacketTransferFromSource:sourceIP destinationIP:destIP packetLength:workItem->header.caplen packetType:ethernet->ether_type];

                break;
            }
        }
}

根据以太网数据包的类型,我试图确定它是使用 IPv4 还是 IPv6 地址发送的数据包。在确定之后,我将一些详细信息发送到 ObjectiveC 方法(源 IP 地址、目标 IP 地址和数据包长度)。

我将数据包转换为 tcpdump 网站 (http://www.tcpdump.org/pcap.html) 上解释的结构。

问题是 pcap 似乎跟不上接收/发送的数据包。要么我没有嗅探所有数据包,要么数据包长度错误。

有没有人有任何指针我需要调整我的代码以使 pcap 全部捕获它们或者我有某种问题。

这些方法是从我的 ObjectiveC 应用程序中调用的,并且refToSelf是对 objC 类的引用。

编辑:我在后台线程中调用 initializeTrafficMonitor,因为 pcap_loop 是阻塞的。

4

1 回答 1

3

这是在哪个版本的 OS X 上?在 Lion 之前的版本中,libpcap 在使用 BPF 的系统(例如 OS X)上的默认缓冲区大小为 32K 字节;1992 年,他们想要他们的 4MB 工作站和 10Mb 以太网。在 Lion 中,Apple 将 libpcap 更新为 1.1.1 版本;在 libpcap 1.1.0 中,默认 BPF 缓冲区大小增加到 512MB(如果不是所有具有 BPF 的系统,大多数系统中的最大值)。

如果这是 Snow Leopard,请尝试切换到新的pcap_create()/ pcap_activate()API,并使用pcap_set_buffer_size()将缓冲区大小设置为 512MB。如果这是Lion或更高版本,那将没有什么不同。

如果您的程序无法跟上平均数据包速率,这将无济于事,但如果临时突发超过平均值,它至少意味着更少的数据包丢失。

如果您的程序无法跟上平均数据包速率,那么,如果您只想要数据包的 IP 地址,请尝试将快照长度(称为“packetSize”`)设置为一个足够大的值以仅捕获IPv4 和 IPv6 的以太网标头和 IP 地址。对于 IPv4,34 字节就足够了(libpcap 或 BPF 可能会将其四舍五入到更大的值),因为这是 14 字节的以太网报头 + 20 字节的 IPv4 报头,没有选项。对于 IPv6,它是 54 字节,对于 14 字节的以太网报头和 40 字节的 IPv6 报头。所以使用 54 的 packetSize 值。

请注意,在这种情况下,您应该使用 的字段len不是caplen字段struct pcap_pkthdr来计算数据包长度。 caplen是捕获的数据量,不会大于指定的快照长度;len是“线上”的长度。

此外,您可能想尝试在同一线程中运行 pcap_loop() 和所有处理,并避免为数据包数据分配缓冲区并复制它,以查看是否加快了处理速度。如果您必须在单独的线程中执行它们,请确保在完成后释放数据包数据。

于 2012-09-02T23:29:28.500 回答