【问题标题】:Set timeout to unresponded hosts in ARP request with libpcap使用 libpcap 为 ARP 请求中未响应的主机设置超时
【发布时间】:2016-12-27 10:41:49
【问题描述】:

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

前言

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

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

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(正如我所说,我从路由器收到请求发送到我的计算机)。现在我的代码怎么了?

【问题讨论】:

    标签: c network-programming libpcap


    【解决方案1】:

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

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

    如果您希望等待 20 毫秒,那么您应该提供值 20 作为 pcap_set_timeout 的第二个参数。然后,您可能希望“手动”接收带有pcap_next_ex 的下一个数据包(而不是调用 pcap 库内的循环的pcap_dispatch)。 pcap_next_ex 的返回值可以让您区分数据包是实际读取还是超时。

    话虽如此,我发现架构很脆弱。我将重组代码如下:

    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);
    

    【讨论】:

    • 第二步是否使用for循环并在循环内调用sendARPRequest?你建议我在哪里添加alarm 功能?在for循环之后?其他步骤的相同问题。如果您想制作代码示例,我将不胜感激。即使是伪代码也会有很大帮助。 multiple iterations 这将是我的下一步。你建议我在循环中添加所有代码来获得它?感谢 Gil 的回复。
    • 嗨,吉尔。我已将pcap_handler 注册为局部变量,因此我无法使用警报回调,因为我无法向其发送用户指定的参数。我可以在循环之后使用sleep 还是有更好的选择?
    • 有什么原因你不能把它放到一个全局变量中? (对模块来说是全局的——它不需要对你的整个程序来说是全局的。)作为替代方案,你可以很容易地用你自己的自定义循环替换pcap_loop。例如,您可以使用 pcap_get_selectable_fd 获取 pcap 库使用的文件描述符,然后使用 select(2) 等待描述符变为可读(在这种情况下,您可以调用 pcap_next_expcap_dispatch获取收到的数据包)或直到超时。
    • 您的实现不起作用。我真的不明白为什么。正如你在我上一篇文章中看到的那样,我有一个可能 arp 包的结构不好的想法。我发布了我当前的代码。如果你想看看它。
    • 您是否收到任何数据包作为响应? Wireshark 是否显示任何被退回?他们是否通过了您的数据包过滤器?
    猜你喜欢
    • 2020-02-11
    • 1970-01-01
    • 2013-11-29
    • 2016-05-06
    • 2014-04-02
    • 2011-04-25
    • 1970-01-01
    • 1970-01-01
    • 2020-11-03
    相关资源
    最近更新 更多