【发布时间】:2014-01-24 02:26:09
【问题描述】:
我正在使用 netfilter 队列库实现用户空间防火墙。我使用nfq_fd() 获得了队列的文件描述符,因此我可以调用recv(fd, recv_buf, BUFFERSIZE, MSG_DONTWAIT) 来获取数据包数据而不会阻塞。但有时recv() 每次调用它时都会开始返回 52 字节的数据包。如果我检查iptables -nvL INPUT 的输出,数据包的数量不会增加,因此它们实际上并不是从网络发送的。 Edit3:nfq_handle_packet() 在我传递其中一个奇数数据包时返回 -1,它从不触发回调函数,因此我无法获取数据包 ID 或返回判决。
为什么 recv() 给我这些奇怪的数据包?
编辑1:
这些数据包并不完全相同,但它们具有相似的结构。也有一些重复。这是其中一些的十六进制转储:
0000 34 00 00 00 02 00 00 00 00 00 00 00 BE 4E 00 00 4............N..
0010 FE FF FF FF 20 00 00 00 01 03 01 00 00 00 00 00 .... ...........
0020 00 00 00 00 00 00 00 00 0C 00 02 00 00 00 00 01 ................
0030 01 00 00 00 ....
0000 34 00 00 00 02 00 00 00 00 00 00 00 5B 69 00 00 4...........[i..
0010 FE FF FF FF 20 00 00 00 01 03 01 00 00 00 00 00 .... ...........
0020 00 00 00 00 00 00 00 00 0C 00 02 00 00 00 00 01 ................
0030 00 00 01 95 ....
0000 34 00 00 00 02 00 00 00 00 00 00 00 5B 69 00 00 4...........[i..
0010 FE FF FF FF 20 00 00 00 01 03 01 00 00 00 00 00 .... ...........
0020 00 00 00 00 00 00 00 00 0C 00 02 00 00 00 00 01 ................
0030 00 00 01 95 ....
编辑2:
代码非常简陋,只是根据我找到的一些 netfilter_queue 教程进行了调整。
#include <linux/netfilter.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <syslog.h>
#define BUFFERSIZE 500
int main()
{
struct nfq_handle *h;
struct nfq_q_handle *qh;
struct my_nfq_data msg;
int fd;
unsigned char recv_buf[BUFFERSIZE];
int action;
if ((stat("/proc/net/netfilter/nfnetlink_queue", &fbuf) < 0) && (errno == ENOENT))
{
fprintf(stderr, "Please make sure nfnetlink_queue is installed, or that you have\ncompiled a kernel with the Netfilter QUEUE target built in.\n");
exit(EXIT_FAILURE);
}
openlog("packetbl", LOG_PID, "local6");
if ((h = nfq_open()) == 0)
{
syslog(LOG_ERR, "Couldn't open netlink connection: %s", strerror(errno));
exit(EXIT_FAILURE);
}
nfq_unbind_pf(h, AF_INET);
if ((nfq_bind_pf(h, AF_INET) < 0))
{
syslog(LOG_ERR, "Couldn't bind to IPv4: %s", strerror(errno));
}
nfq_unbind_pf(h, AF_INET6);
if ((nfq_bind_pf(h, AF_INET6) < 0))
{
syslog(LOG_ERR, "Couldn't bind to IPv6: %s", strerror(errno));
}
if ((qh = nfq_create_queue(h, 0, &callback, &msg)) == NULL)
{
syslog(LOG_ERR, "Couldn't create nfq: %s", strerror(errno));
exit(EXIT_FAILURE);
}
if ((nfq_set_mode(qh, NFQNL_COPY_PACKET, BUFFERSIZE)) == -1)
{
syslog(LOG_ERR, "nfq_set_mode error: %s", strerror(errno));
if (errno == 111)
{
syslog(LOG_ERR, "try loading the nfnetlink_queue module");
}
exit(EXIT_FAILURE);
}
fd = nfq_fd(h);
while(1)
{
/* Up here I print some statistics on packets allowed and blocked.
It prints on a schedule, so the recv() call has to be non-blocking
or else the statistics would only print out when there's a packet. */
recv_return_code = recv(fd, recv_buf, BUFFERSIZE, MSG_DONTWAIT); //nonblocking
if (recv_return_code < 0)
{
if (errno == EAGAIN ||
errno == EWOULDBLOCK)
{
nanosleep(×,NULL);
}
else
{
syslog(LOG_ERR, "recv failed: %s", strerror(errno));
}
continue;
}
printf("received %d bytes\n", recv_return_code);
/* when nfq_handle_packet() succeeds, it triggers the callback
which puts the packet data into a global variable "msg" */
if (nfq_handle_packet(h, recv_buf, recv_return_code) != 0)
{
syslog(LOG_ERR, "couldn't handle packet");
}
action = packet_check_ip(msg);
pbl_set_verdict(qh, ntohl(msg.header.packet_id), action);
}
}
编辑 4:
我使用 scapy 作为流量生成器。如果我一次只发送一个数据包,那么我会收到 0 或 1 个虚假数据包,然后它就会停止。这是 strace 的输出:
recvfrom(3, "x\0\0\0\0\3\0\0\0\0\0\0\0\0\0\0\n\0\0\0\v\0\1\0\0\0\0\6\206\335\1\0\10\0\5\0\0\0\0\2\20\0\t\0\0\6\261\201\0\f)7Z\22\0\0@\0\n\0`\0\0\0\0\24\6@&\6\364\0\10\0\0\0\0\0\0\0\0\0p\5&\6\364\0\10\0\0\0\0\0\0\0\0\0p\4\0\24\0\31\0\0\0\0\0\0\0\0P\2 \0k\236\0\0", 9216, MSG_DONTWAIT, NULL, NULL) = 120
sendto(4, "<182>Jan 13 10:51:20 packetbl[8785]: [Found in cache (accept)] [2606:f400:800::7005,20,25]", 90, MSG_NOSIGNAL, NULL, 0) = 90
sendmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{" \0\0\0\1\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\f\0\2\0\0\0\0\1\0\0\0\6", 32}], msg_controllen=0, msg_flags=0}, 0) = 32
recvfrom(3, "x\0\0\0\0\3\0\0\0\0\0\0\0\0\0\0\n\0\0\0\v\0\1\0\0\0\0\7\206\335\1\0\10\0\5\0\0\0\0\2\20\0\t\0\0\6\261\201\0\f)7Z\22\0\0@\0\n\0`\0\0\0\0\24\6@&\6\364\0\10\0\0\0\0\0\0\0\0\0p\1&\6\364\0\10\0\0\0\0\0\0\0\0\0p\4\0\24\0\31\0\0\0\0\0\0\0\0P\2 \0k\242\0\0", 9216, MSG_DONTWAIT, NULL, NULL) = 120
futex(0x60c984, FUTEX_CMP_REQUEUE_PRIVATE, 1, 2147483647, 0x607fc0, 8) = 2
futex(0x607fc0, FUTEX_WAKE_PRIVATE, 1) = 1
sendmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{" \0\0\0\1\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\f\0\2\0\0\0\0\1\7\0\0\0", 32}], msg_controllen=0, msg_flags=0}, 0) = 32
recvfrom(3, "4\0\0\0\2\0\0\0\0\0\0\0Q\"\0\0\376\377\377\377 \0\0\0\1\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\f\0\2\0\0\0\0\1\7\0\0\0", 9216, MSG_DONTWAIT, NULL, NULL) = 52
sendto(4, "<179>Jan 13 10:51:22 packetbl[8785]: couldn't handle packet", 59, MSG_NOSIGNAL, NULL, 0) = 59
sendmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{" \0\0\0\1\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\f\0\2\0\0\0\0\1\0\0\0\7", 32}], msg_controllen=0, msg_flags=0}, 0) = 32
我可以像转动手指一样快地发送单个数据包,而且它永远不会陷入死亡螺旋。但是,如果我一次发送 4 个数据包,它有时会为每个真实数据包触发一个(或零个)虚假数据包,但有时我会收到无限的虚假数据包。如果我发送很多数据包,它总是无限的。
我以前见过一些这种行为,但 Nominal Animal 的回答唤起了我的记忆。如上所示,关于我的代码的一件奇怪的事情是,即使nfq_handle_packet() 失败,我仍然会执行packet_check_ip() 和pbl_set_verdict()。我认为在这种情况下放置continue; 是有意义的,因为否则我将在msg 变量中处理陈旧数据。 (如果我错了,请纠正我,但这应该与将数据包处理和判决移到回调中具有相同的效果。)但这始终会在 1 个真实数据包之后引发无限的虚假数据包。我也将判决暂时移到回调中,它没有改变任何东西。
那么不知何故,对旧数据调用 set_verdict 有时会阻止无穷大?
哦,这里是pbl_set_verdict() 的代码,如果有人担心它可能会做任何聪明的事情:)
static void pbl_set_verdict(struct nfq_q_handle *qh,
uint32_t id,
unsigned int verdict)
{
nfq_set_verdict(qh, id, verdict, 0, NULL);
}
编辑 5:
我已经编译并运行了与 libnetfilter_queue 一起分发的 nfqnl_test.c 示例,它运行得很好。所以这可能不是库本身的问题。
编辑 6:
现在我到了某个地方 :) 事实证明,在容量过剩的情况下,ntohl() 被调用了两次!而且因为即使nfq_handle_packet 失败,我也在对陈旧数据调用pbl_set_verdict(),它正确地运行了数据,从而产生了正确的效果。这就是为什么当我将pbl_set_verdict() 调用移至回调函数时队列已满的原因——它从未有机会解决由容量过剩情况引起的问题。而且陈旧的数据只包括一些处理过的数据包,所以无论如何最终都会有一堆数据包填满队列。
即使我的程序现在可以运行,我仍然对这些数据包是什么以及为什么它们似乎没有记录在案感到困惑。
【问题讨论】:
-
您是否尝试过打印数据包?它们都一样吗?
-
在 Wireshark 中你看不到那些数据包? TCP/UDP?那些包的内容是什么?每个都不一样?
-
我的疯狂猜测是,您忽略了在回调函数中对某些接收到的数据包设置判断,并且内核吓坏了,快用完了缓冲区可能——我可能弄错了,但我认为内核可能只重新发送配置消息中的数据包 ID 而不是整个数据包。为了得到真正的答案,我们需要详细的信息和足够的代码来重现问题。否则我们都只是在浪费时间。至少,需要一些此类数据包和关键代码行的十六进制转储。
-
是的,这是一个交叉点。一个多星期以来,我在 SO 上有几个版本的这个问题都没有答案,所以我也将它发送到了邮件列表。
标签: c linux iptables netfilter