我正在使用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:
我用替罪羊做交通发生器。如果我一次只发送一个数据包,那么我会得到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个包,它有时会触发一个(或零)假包为每个真正的包,但其他时候,我收到无限的假包。如果我发送大量的数据包,它总是无限的。
我以前见过一些这样的行为,但是名义动物的回答却让我记忆犹新。正如上面所示,关于我的代码的一个奇怪之处是,即使packet_check_ip()和pbl_set_verdict()失败,我仍然会执行nfq_handle_packet()和nfq_handle_packet()。我认为在这种情况下放置一个continue;是有意义的,因为否则我将在msg变量中处理陈旧的数据。(如果我错了,请纠正我,但这与将数据包处理和裁决移到回调中具有相同的效果。)但是,即使在一个真正的数据包之后,这也会不断地引发无限大的虚假数据包。我还将判决暂时移到回调中,但没有改变任何事情。
因此,在旧数据上调用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:
我编译并运行了使用nfqnl_test.c分发的libnetfilter_queue示例,它运行得很好。所以这可能不是图书馆本身的问题。
编辑6:
现在我已经有所进展了:)原来,在容量过大的情况下,ntohl()被调用了两次!而且,即使在pbl_set_verdict()失败时,我也在对陈旧的数据调用nfq_handle_packet,所以它正确地运行了数据,并产生了正确的效果。这就是为什么当我将pbl_set_verdict()调用移到回调函数中时,队列被填满了--它从来没有机会修复过大容量条件造成的问题。陈旧的数据只包含一些处理过的数据包,所以它们最终会填满队列。
尽管我的程序现在起作用了,但我仍然对这些包是什么以及它们为什么没有文档化感到困惑。
发布于 2014-01-10 04:44:13
将您的代码与示例在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;
}
}因为回调基本上是
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子句中的“打印总体统计”注释之前,线程是否应该退出;然后您可以简单地设置标志,并发送更新统计信息的信号,使所有工作线程退出,而不丢弃任何“在地板上”的数据包。
有什么问题吗?
https://stackoverflow.com/questions/20954110
复制相似问题