我正在使用netfilter队列库实现用户空间防火墙。我使用nfq_fd()
获得了队列的文件描述符,因此我可以调用recv(fd, recv_buf, BUFFERSIZE, MSG_DONTWAIT)
来获取数据包数据而不会阻塞。但有时recv()
每次调用时都会返回52字节的数据包。如果我检查iptables -nvL INPUT
的输出,数据包的数量不会增加,那么它们实际上并不是从网络发送的。 Edit3:当我传递其中一个奇数数据包时,nfq_handle_packet()返回-1,它永远不会触发回调函数,所以我无法获取数据包ID或返回判决。
为什么recv()会给我这些奇怪的数据包?
EDIT1:
数据包并非完全相同,但它们具有相似的结构。还有一些重复。这是其中几个的十六进制:
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 ....
EDIT2:
代码非常简陋,只是从我发现的一些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
我可以尽可能快地发送个别数据包,因为它可以旋转我的手指,它永远不会陷入死亡螺旋。但是,如果我有scapy一次发送4个数据包,它有时会为每个真实数据包触发一个(或零)伪造数据包,但有时我收到无限伪造数据包。如果我发送大量数据包,它总是无限的。
之前我曾见过这种行为,但Nominal Animal的回答让我记忆犹新。关于我的代码的一个奇怪的事情如上所示,即使packet_check_ip()
失败,我仍然会pbl_set_verdict()
和nfq_handle_packet()
。我认为在这种情况下放置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()
被调用了两次!而且因为即使pbl_set_verdict()
失败,我也会在陈旧数据上调用nfq_handle_packet
,因此它会正确地运行数据,这会产生正确的效果。这就是当我将pbl_set_verdict()
调用移动到回调函数时队列填满的原因 - 它从来没有机会解决由于容量过大而引起的问题。过时的数据只包括一些处理过的数据包,所以无论如何它们中的一堆最终会填满队列。
即使我的程序现在正常运行,我仍然对这些数据包是什么以及为什么它们似乎没有被记录感到困惑。
答案 0 :(得分:3)
将您的代码与libnetfilter_queue来源的example进行比较。您的代码设置了判决(假设您的代码中的pbl_set_verdict()
为 >>处理完数据包后)。该示例在回调函数中设置判定。
我对netfilter内脏没有足够的信心确定这是你问题的根本原因,但我确实相信它。
关于使用非阻塞读取,不需要这样做。相反,让间隔定时器定期触发信号(例如,HUP
或实时信号,如SIGRTMIN+1
),并为该信号安装空信号处理函数。当信号被传递(到空体处理程序; IGN
或DFL
将不起作用)时,这会导致任何阻塞I / O调用被中断,假设您的进程只有一个线程。如果间隔很长,则使用HUP
非常有用,因为用户可以在外部发送HUP以使统计信息立即打印。这种方式不会浪费额外的CPU时间。
如果您的应用程序使用多个线程,则需要更多的机器。处理程序需要检查源是否是定时器中断(siginfo->si_code==SI_TIMER
),如果是,则使用pthread_sigqueue()
将中断(相同信号)转发到目标线程,除非当前线程是目标线程。然后,通过netlink读取消息的线程需要将其线程ID保存到中断处理程序可以访问它们的某个地方。 (此外,您的其他代码必须知道errno==EINTR
可能会发生,并且不是错误,除非它们专门阻止信号。)
换句话说,我希望你的代码更像是
/* In case of an error, break out of the following loop.
* You can either exit, or close and re-establish the netlink
* and queue.
*/
while(1)
{
ssize_t bytes;
/* Read a new netlink message.
Note: Technically, BUFFERSIZE should be about 65536,
since each message has a uint16_t message length field.
*/
bytes = recv(fd, recv_buf, BUFFERSIZE, MSG_DONTWAIT);
/* C library, or kernel recv() bug?
*/
if (bytes < (ssize_t)-1 || bytes > (ssize_t)BUFFERSIZE) {
errno = EIO;
break; /* out of the while (1) loop */
}
/* Netlink closed? Should not occur. */
if (bytes == (ssize_t)0) {
errno = 0;
break; /* No error, just netlink closed. Drop out. */
}
/* No message? */
if (bytes == (ssize_t)-1) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
/* Print overall statistics.
*/
continue;
} else
break; /* Other errors drop out of the loop. */
}
if (nfq_handle_packet(h, recv_buf, bytes)) {
/* Packet was dropped on the floor.
* This is a serious problem, so we treat this as EIO.
*/
errno = EIO;
break;
}
}
回调基本上是
static int callback(struct nfq_q_handle *qh,
struct nfgenmsg *nfmsg,
struct nfq_data *nfa,
void *data)
{
return nfq_set_verdict(qh, id, packet_check_ip(nfmsg), 0, NULL);
}
对于上面的多线程,你可以简单地让多个线程同时运行上面的循环(显然有不同的recv_buf
缓冲区)。然后,接收数据包的线程也处理它,包括回调。除非您自己的代码是非线程安全的,否则线程安全应该没有问题。如果线程应该退出,也可以在if子句中的“打印整体统计信息”注释之前添加一个检查(对于某些全局易失性标记);然后你可以简单地设置标志,并发送信号来更新统计数据,让所有工作线程退出,而不会丢弃任何“在地板上”的数据包。
有问题吗?