sep*_*332 6 c linux iptables netfilter
我正在使用netfilter队列库实现用户空间防火墙.我得到了队列的文件描述符,nfq_fd()所以我可以调用recv(fd, recv_buf, BUFFERSIZE, MSG_DONTWAIT)来获取数据包数据而不会阻塞.但有时recv()每次调用时都会返回52字节的数据包.如果我检查iptables -nvL INPUT数据包的输出没有增加,那么它们实际上并不是从网络发送的.Edit3:nfq_handle_packet()在我传递其中一个奇数数据包时返回-1,并且它永远不会触发回调函数,因此我无法获取数据包ID或返回判决.
为什么recv()给我这些奇数包?
EDIT1:
数据包并非完全相同,但它们具有相似的结构.还有一些重复.这是其中几个的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 ....
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
编辑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
Run Code Online (Sandbox Code Playgroud)
我可以尽可能快地发送个别数据包,我可以旋转我的手指,它永远不会陷入死亡螺旋.但是,如果我有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);
}
Run Code Online (Sandbox Code Playgroud)
编辑5:
我编译并运行了随libnetfilter_queue一起发布的nfqnl_test.c示例,它运行得很好.所以它可能不是图书馆本身的问题.
编辑6:
现在我到了某个地方:)事实证明,在容量过剩的情况下,ntohl()被调用了两次!而且因为我pbl_set_verdict()即使在nfq_handle_packet失败时也会调用过时的数据,它会正确地运行数据并产生正确的效果.这就是当我将pbl_set_verdict()调用移动到回调函数时队列填满的原因- 它从来没有机会解决由于容量过大而导致的问题.过时的数据只包括一些处理过的数据包,所以最终它们中的一堆数据库最终会填满队列.
即使我的程序现在正常工作,我仍然感到困惑的是这些数据包是什么以及为什么它们似乎没有被记录.
将您的代码与libnetfilter_queue 源中的示例进行比较。您的代码在处理数据包后设置判决(假设这就是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;
}
}
Run Code Online (Sandbox Code Playgroud)
回调基本上是
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);
}
Run Code Online (Sandbox Code Playgroud)
至于上面的多线程,您可以简单地让多个线程同时运行上述循环(recv_buf显然使用不同的缓冲区)。然后,接收数据包的线程也会处理它,包括回调。线程安全不应该有问题,除非您自己的代码是非线程安全的。您还可以在 if 子句中的“打印总体统计信息”注释之前添加一个检查(对于某些全局易失性标志)线程是否应退出;那么你可以简单地设置标志,并发送信号来更新统计数据,让所有工作线程退出,而不会“地上”丢弃任何数据包。
任何问题?