使用 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(正如我所说,我收到从我的路由器发送到我的计算机的请求)。现在我的代码怎么了?
您当前的实现永远阻塞,因为您使用 -1
的 cnt
参数调用 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 值允许您区分数据包是否实际读取或超时已过期。
综上所述,我发现架构很脆弱。我将重组代码如下:
列出您要向其发送 ARP 的所有主机。 (你已经在这样做了,所以真的没有什么额外的。)
向列表中的每个主机发送 ARP 请求。
设置外部超时(用alarm(2)
说)。
调用 pcap_loop
处理传入的数据包。
对于收到的每个ARP响应(在callback
函数中),通过扫描列表找到合适的主机,并记录你从响应中得到的MAC地址。
当超时到期时,调用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);
我用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(正如我所说,我收到从我的路由器发送到我的计算机的请求)。现在我的代码怎么了?
您当前的实现永远阻塞,因为您使用 -1
的 cnt
参数调用 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 值允许您区分数据包是否实际读取或超时已过期。
综上所述,我发现架构很脆弱。我将重组代码如下:
列出您要向其发送 ARP 的所有主机。 (你已经在这样做了,所以真的没有什么额外的。)
向列表中的每个主机发送 ARP 请求。
设置外部超时(用
alarm(2)
说)。调用
pcap_loop
处理传入的数据包。对于收到的每个ARP响应(在
callback
函数中),通过扫描列表找到合适的主机,并记录你从响应中得到的MAC地址。当超时到期时,调用
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);