使用 libpcap 为 ARP 请求中未响应的主机设置超时

Set timeout to unresponded hosts in ARP request with libpcap

我用C做了一个简单的arp请求程序。我根据我的IP和子网掩码生成一个主机列表,并为每个ip发送arp请求。最后,我收集了结果并将响应的计算机 MAC 地址打印到屏幕上。

前言

我使用libpcap C 中的实现来处理发送和接收数据包的过程。

首先我创建一个处理程序。

pcap_t pcapHandler;
if (!(pcapHandler = pcap_create(interfaceName, errorBuffer))) {
    error(errorBuffer, EXIT_FAILURE);
  }
  if ((pcap_set_snaplen(pcapHandler, 64)) < 0) {
    error(pcap_geterr(pcapHandler), EXIT_FAILURE);
  }
  if ((pcap_set_promisc(pcapHandler, 1)) < 0) {
    error(pcap_geterr(pcapHandler), EXIT_FAILURE);
  }
  if ((pcap_set_timeout(pcapHandler, 0)) < 0) {
    error(pcap_geterr(pcapHandler), EXIT_FAILURE);
  }
  if ((pcap_activate(pcapHandler)) < 0) {
    error(pcap_geterr(pcapHandler), EXIT_FAILURE);
  }

然后我附加了一个过滤器以仅接收对我的计算机的 arp 响应

  struct bpf_program filter;
  char *filter_string;
  filter_string = makeMessage("ether dst %.2x:%.2x:%.2x:%.2x:%.2x:%.2x and "
                              "(arp or (ether[14:4]=0xaaaa0300 and "
                              "ether[20:2]=0x0806) or (ether[12:2]=0x8100 "
                              "and ether[16:2]=0x0806) or "
                              "(ether[12:2]=0x8100 and "
                              "ether[18:4]=0xaaaa0300 and "
                              "ether[24:2]=0x0806))",
                              interface_mac[0], interface_mac[1],
                              interface_mac[2], interface_mac[3],
                              interface_mac[4], interface_mac[5]);

  if ((pcap_compile(pcapHandler, &filter, filter_string, 1, subnet_mask)) < 0) {
    error(pcap_geterr(pcapHandler), EXIT_FAILURE);
  }
  if ((pcap_setfilter(pcapHandler, &filter)) < 0) {
    error(pcap_geterr(pcapHandler), EXIT_FAILURE);
  }

最后我使用了一个用户定义的函数来准备arp请求。我只是打电话

 sent = pcap_sendpacket(pcapHandler, ARPBuffer, ARPBufferLength);

  if (sent < 0) {
    error(pcap_geterr(pcapHandler), EXIT_FAILURE);
  }

之后发送数据包。

我的问题是在接收arp响应(如果有)的阶段。

我使用for循环遍历每个主机地址并向其发送arp数据包。

HE *currHost; // list of my current host informations.
  for (currHost = hostListHead; currHost != NULL; currHost = currHost->next) {
    printf("Testing: %s\n", inet_ntoa(currHost->addr));

    // function that take the handler the current host details and my wireless interface I use to generate and send arp packages
    sendARPRequest(pcapHandler, currHost, workingWI);

    // dispatcher
    pcap_dispatch(pcapHandler, -1, callback, NULL);
  }

然后在回调函数中我得到响应并继续它。

问题

我的循环堆栈在每台主机上。假设我在某个不包含任何计算机的地址中发送了一个 arp 请求。我的程序不会继续到下一个主机,而是等待得到响应。只有当我从我的路由器收到响应时,程序才会继续使用另一台主机(为什么这可能?)

有什么方法可以设置超时吗?所以当我没有得到任何回应让我们说 20 ms 继续下一个?但我不知道即使我可以在客户端响应时处理数据包。我的实现有问题吗?

更新

在 Gil 提出后,这是我使用的新代码,我又遇到了同样的问题:

pcap_t *pcapHandler;

void alarm_handler() {
  pcap_breakloop(pcapHandler);
}

void startLocalMode(WI **workingWI) {
  HE *hostListHead;
  int numberOfHosts = generateHostList(&hostListHead, (*workingWI)->address, (*workingWI)->subnet_mask);

  
  preparePCAPHandler(&pcapHandler, (*workingWI)->name);
  setPCAPHandlerFilter(&pcapHandler, (*workingWI)->interface_mac, (*workingWI)->subnet_mask);


  HE *currHost;
  int i = 0;
  for (currHost = hostListHead; i < numberOfHosts; currHost = currHost->next) {
    sendARPRequest(pcapHandler, currHost, (*workingWI));
    ++i;
  }
  alarm(2);
  signal(SIGALRM, alarm_handler);
  pcap_loop(pcapHandler, 0, handleARPresponce, (u_char*)hostListHead);
}

这是回调函数:

#define ETHER_HDR_SIZE 14   /* Size of Ethernet frame header in bytes */
#define ARP_PKT_SIZE 28 /* Size of ARP Packet in bytes */

void handleARPresponce(u_char *args, const struct pcap_pkthdr *header, const u_char *packet_in) {
  int n = header->caplen;
  if (n < ETHER_HDR_SIZE + ARP_PKT_SIZE) {
    //printw("%d byte packet too short to decode\n", n);
    return;
  }

  arp_ether_ipv4 arpei;
  ether_hdr frame_hdr;
  struct in_addr source_ip;
  HE *temp_cursor;
  unsigned char extra_data[MAX_FRAME];
  size_t extra_data_len;
  int vlan_id;
  int framing;

  framing = unpackageARP(packet_in, n, &frame_hdr, &arpei, extra_data, &extra_data_len, &vlan_id);
  source_ip.s_addr = arpei.spa;

  temp_cursor = findHost((HE *)args, &source_ip);
  if (temp_cursor) {
    displayARPresponce(temp_cursor, &arpei, &frame_hdr);
  }
  return;
}

回调函数正确处理响应c(正如我所说,我收到从我的路由器发送到我的计算机的请求)。现在我的代码怎么了?

您当前的实现永远阻塞,因为您使用 -1cnt 参数调用 pcap_dispatch,这实际上表示循环直到收到 "buffer full" 个数据包。我相信会等到至少收到一个数据包,所以如果你没有收到 ARP 响应,它将永远等待。

您已经在使用 pcap_set_timeout。您当前将超时设置为 0。我怀疑值 0 会导致无限等待。 (pcap(3) 手册页说要避免超时值为 0。关于使用超时还有其他注意事项。)

如果您希望等待 20 毫秒,则应提供值 20 作为 pcap_set_timeout 的第二个参数。然后,您可能希望 "manually" 使用 pcap_next_ex 接收下一个数据包(而不是 pcap_dispatch ,它会调用 pcap 库中的循环)。来自 pcap_next_ex 的 return 值允许您区分数据包是否实际读取或超时已过期。

综上所述,我发现架构很脆弱。我将重组代码如下:

  1. 列出您要向其发送 ARP 的所有主机。 (你已经在这样做了,所以真的没有什么额外的。)

  2. 向列表中的每个主机发送 ARP 请求。

  3. 设置外部超时(用alarm(2)说)。

  4. 调用 pcap_loop 处理传入的数据包。

  5. 对于收到的每个ARP响应(在callback函数中),通过扫描列表找到合适的主机,并记录你从响应中得到的MAC地址。

  6. 当超时到期时,调用pcap_breakloop(从信号处理程序,比如说)退出pcap_loop。 (您可能仍然需要调用 pcap_set_timeout 以确保 pcap_breakloop 生效——请参阅 pcap_breakloop(3) 手册页进行讨论)。

实际上,您正在以这种方式并行处理所有主机。最后,您有一个主机列表,每个主机都有一个关联的 MAC 地址 如果 ARP 成功。

事实上,如果这是稳健的,我会更进一步 运行 如有必要,这会进行多次迭代——也就是说,将 ARP 请求重新发送到您没有从中发送的机器'没有得到响应 -- 以防请求或响应在您的网络中的某处丢失。

伪代码:

alarm_handler()
{
  pcap_breakloop(pcap_handler)
}

callback(u_char *user, const struct pcap_pkthdr *hdr, const u_char *data)
{
  for (host in host_list) {
    if (host->IP == IP_from_ARP_packet(data)) {
      host->MAC = MAC_from_ARP_packet(data);
      host->MAC_is_known = true;
      break;
    }
  }
}

run_sweep(host_list)
{
  for (host in host_list) {
    if (!host->MAC_is_known)
      sendARPRequest(pcapHandler, host...);
  }
  alarm(SWEEP_TIMEOUT);
  signal(SIGALRM, alarm_handler);
  pcap_loop(pcapHandler, 0, callback, NULL);
}

/* Main code */
for (host in host_list)
  host->MAC_is_known = false;

sweeps = 0;
while (sweeps++ < MAX_SWEEPS && !all_hosts_MAC_known(host_list))
  run_sweep(host_list);