gpt4 book ai didi

c - netfilter_queue 虚假数据包

转载 作者:IT王子 更新时间:2023-10-29 00:56:35 30 4
gpt4 key购买 nike

我正在使用 netfilter 队列库实现用户空间防火墙。我使用 nfq_fd() 获得了队列的文件描述符,因此我可以调用 recv(fd, recv_buf, BUFFERSIZE, MSG_DONTWAIT) 来获取数据包数据而不会阻塞。但有时 recv() 在我每次调用它时都会开始返回 52 字节的数据包。如果我检查 iptables -nvL INPUT 的输出,数据包的数量不会增加,所以它们实际上并不是从网络发送的。 Edit3:当我向 nfq_handle_packet() 传递其中一个奇怪的数据包时,它返回 -1,并且它永远不会触发回调函数,因此我无法获取数据包 ID 或返回结论。

为什么 recv() 给我这些奇怪的数据包?

编辑1:

数据包并不完全相同,但它们具有相似的结构。也有一些重复。这是其中一些的 hexdump:

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(&times,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 的回答勾起了我的内存。如上所示,关于我的代码的一件奇怪的事情是,即使 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() 调用移入回调函数时队列已满的原因 - 它从未有机会解决由容量过剩情况引起的问题。陈旧的数据只包括一些已处理的数据包,所以无论如何它们最终都会填满队列。

即使我的程序现在可以运行,我仍然对这些数据包是什么以及为什么它们似乎没有记录在案感到困惑。

最佳答案

将您的代码与 libnetfilter_queue 源代码中的 example 进行比较。您的代码设置判决(假设这是您代码中的 pbl_set_verdict() 所做的)处理数据包之后。该示例在回调函数中设置判断。

我对 netfilter 的内部结构没有足够的信心可以肯定地说这是你问题的根本原因,但我确实相信它是。

至于使用非阻塞读,没有那个必要。取而代之的是,让间隔计时器定期触发一个信号(比如 HUP 或像 SIGRTMIN+1 这样的实时信号),并为该信号安装一个空信号处理函数.当信号被传送时(到空体处理程序;IGNDFL 将不起作用),这会导致任何阻塞的 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 子句内的 “打印总体统计信息” 注释之前添加一个检查(对于某些全局 volatile 标志);然后你可以简单地设置标志,并发送信号来更新统计数据,让所有工作线程退出,而不会将任何数据包丢弃在“地板上”。

有什么问题吗?

关于c - netfilter_queue 虚假数据包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20954110/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com