在不使用环回networking的情况下将数据包转发到同一台主机上

我有这个libnetfilter_queue应用程序接收来自内核的一些iptables规则的数据包。 在直接回答我的问题之前,我给了一个示例代码和其他工具来设置一个testing环境,以便我们的问题定义和可能的解决scheme可以更加准确和强大。

以下代码描述了应用程序的核心function:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> #include <linux/types.h> #include <linux/netfilter.h> /* for NF_ACCEPT */ #include <errno.h> #include <libnetfilter_queue/libnetfilter_queue.h> #define PREROUTING 0 #define POSTROUTING 4 #define OUTPUT 3 /* returns packet id */ static u_int32_t print_pkt (struct nfq_data *tb) { int id = 0; struct nfqnl_msg_packet_hdr *ph; struct nfqnl_msg_packet_hw *hwph; u_int32_t mark, ifi; int ret; unsigned char *data; ph = nfq_get_msg_packet_hdr (tb); if (ph) { id = ntohl (ph->packet_id); printf ("hw_protocol=0x%04x hook=%u id=%u ", ntohs (ph->hw_protocol), ph->hook, id); } hwph = nfq_get_packet_hw (tb); if (hwph) { int i, hlen = ntohs (hwph->hw_addrlen); printf ("hw_src_addr="); for (i = 0; i < hlen - 1; i++) printf ("%02x:", hwph->hw_addr[i]); printf ("%02x ", hwph->hw_addr[hlen - 1]); } mark = nfq_get_nfmark (tb); if (mark) printf ("mark=%u ", mark); ifi = nfq_get_indev (tb); if (ifi) printf ("indev=%u ", ifi); ifi = nfq_get_outdev (tb); if (ifi) printf ("outdev=%u ", ifi); ifi = nfq_get_physindev (tb); if (ifi) printf ("physindev=%u ", ifi); ifi = nfq_get_physoutdev (tb); if (ifi) printf ("physoutdev=%u ", ifi); ret = nfq_get_payload (tb, &data); if (ret >= 0) printf ("payload_len=%d ", ret); fputc ('\n', stdout); return id; } static int cb (struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { uint32_t ip_src, ip_dst; struct in_addr s_ip; struct in_addr d_ip; uint16_t src_port; uint16_t dst_port; int verdict; int id; int ret; unsigned char *buffer; struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr (nfa); if (ph) { id = ntohl (ph->packet_id); printf ("received packet with id %d", id); } ret = nfq_get_payload (nfa, &buffer); ip_src = *((uint32_t *) (buffer + 12)); ip_dst = *((uint32_t *) (buffer + 16)); src_port = *((uint16_t *) (buffer + 20)); dst_port = *((uint16_t *) (buffer + 22)); s_ip.s_addr = (uint32_t) ip_src; d_ip.s_addr = (uint32_t) ip_dst; *(buffer + 26) = 0x00; *(buffer + 27) = 0x00; printf ( "source IP %s", inet_ntoa (s_ip)); printf ( "destination IP %s", inet_ntoa (d_ip)); printf ( "source port %d", src_port); printf ( "destination port %d", dst_port); if (ret) { switch (ph->hook) { case PREROUTING: printf ( "inbound packet"); //my_mangling_fun(); break; case OUTPUT: printf ( "outbound packet"); //my_mangling_fun(); break; } } verdict = nfq_set_verdict (qh, id, NF_ACCEPT, ret, buffer); if (verdict) printf ( "verdict ok"); return verdict; } int main (int argc, char **argv) { struct nfq_handle *h; struct nfq_q_handle *qh; struct nfnl_handle *nh; int fd; int rv; char buf[4096] __attribute__ ((aligned)); printf ("opening library handle\n"); h = nfq_open (); if (!h) { fprintf (stderr, "error during nfq_open()\n"); exit (1); } printf ("unbinding existing nf_queue handler for AF_INET (if any)\n"); if (nfq_unbind_pf (h, AF_INET) < 0) { fprintf (stderr, "error during nfq_unbind_pf()\n"); exit (1); } printf ("binding nfnetlink_queue as nf_queue handler for AF_INET\n"); if (nfq_bind_pf (h, AF_INET) < 0) { fprintf (stderr, "error during nfq_bind_pf()\n"); exit (1); } printf ("binding this socket to queue '0'\n"); qh = nfq_create_queue (h, 0, &cb, NULL); if (!qh) { fprintf (stderr, "error during nfq_create_queue()\n"); exit (1); } printf ("setting copy_packet mode\n"); if (nfq_set_mode (qh, NFQNL_COPY_PACKET, 0xffff) < 0) { fprintf (stderr, "can't set packet_copy mode\n"); exit (1); } fd = nfq_fd (h); for (;;) { if ((rv = recv (fd, buf, sizeof (buf), 0)) >= 0) { printf ("pkt received\n"); nfq_handle_packet (h, buf, rv); continue; } /* if your application is too slow to digest the packets that * are sent from kernel-space, the socket buffer that we use * to enqueue packets may fill up returning ENOBUFS. Depending * on your application, this error may be ignored. Please, see * the doxygen documentation of this library on how to improve * this situation. */ if (rv < 0 && errno == ENOBUFS) { printf ("losing packets!\n"); continue; } perror ("recv failed"); break; } printf ("unbinding from queue 0\n"); nfq_destroy_queue (qh); #ifdef INSANE /* normally, applications SHOULD NOT issue this command, since * it detaches other programs/sockets from AF_INET, too ! */ printf ("unbinding from AF_INET\n"); nfq_unbind_pf (h, AF_INET); #endif printf ("closing library handle\n"); nfq_close (h); exit (0); } 

注意在callback函数中my_mangling_fun()的两个调用被注释掉了。 这是我打开传入和传出的数据包。 我认为这个代码足以描述我的情况。 如果需要进一步澄清,请问,我会发布进一步的细节。

让我们说随行的iptables规则如下:

 $iptables -t mangle -A PREROUTING -p udp --dport 5000 -j NFQUEUE $iptables -t mangle -A OUTPUT -p udp --sport 5000 -j NFQUEUE 

让编译和启动udp的东西。

 $gcc -g3 nfq_test.c -lnfnetlink -lnetfilter_queue $./a.out (should be as root) 

现在我们可以通过netcat客户端和服务器模式向这个东西提供垃圾udp负载

 $nc -ul 5000 $nc -uvv <IP> 5000 

这将打印从我的netfilter_queue应用程序在标准输出的数据包。 现在开发环境已经build立起来,我们可以转向下一步。

我们正在努力实现的是:

我们的服务器正在监听5000端口。 现在所有发往udp端口的数据包都将被内核排队。 而这个队列的句柄将被赋予我们前面列出的用户应用程序。 这种队列机制是这样工作的:当一个数据包可用时,调用callback函数(在我们的代码中的cb())。 处理完成后,callback函数调用nfq_set_verdict()。 判决返回后,下一个数据包将从队列中popup。 注意,如果一个数据包的前一个数据包没有被判决出来,它将不会从队列中popup。 这个判定值是用于接收数据包的NF_ACCEPT,用于丢弃数据包的NF_DROP。

现在,如果我想连接传入和传出数据包的udp负载而不接触客户端和服务器端代码呢?

如果我想从我们的应用程序这个非常的应用程序连接udp有效载荷,那么我们需要手头有多个数据包。 但是我们已经看到,在判决被发送到前一个之前,数据包不会从队列中popup。

那么这怎么做呢?

一种可能的解决scheme是对每个数据包发出一个NF_DROP,并将这些数据包保存在中间数据结构中。 比方说,我们已经做到了。 但是这个数据包怎么能被传送到监听5000端口的服务呢?

我们不能使用networking堆栈来传递数据包,因为如果我们这样做,那么数据包将再次以NFQUEUE结尾。

另一个问题是,服务器是完全不知道这个应用程序。 这意味着它不应该在数据包中看到任何差异。 它应该看到数据包,就好像它来自原始客户端。

我听说应用程序可以通过写入一些文件,而不使用networking层(ip,port),将数据发送到同一主机上的服务器。 我不知道这个声明的有效性。 但如果有人知道这件事,那将是美好的。

我可能会赞成太多的冗长。 但我认为这可以是有趣的会议。 我们可以一起find解决scheme:)

我提出以下解决方案:

  • 将数据包存储在应用程序中并返回结果NF_DROP
  • 使用RAW套接字将数据包重新注入网络堆栈
  • 使用DSCP标记串联的UDP数据包(请参阅IP数据包格式)
  • 在iptables中,添加一个规则来匹配这个DSCP(–dscp)并直接接受数据包,而不通过你的netfilter应用

如果您的提供商已经使用DSCP标记了一些数据包,则可以添加一些iptables规则来清除它们,如:

 iptables -t mangle -A INPUT -j DSCP --set-dscp 0 

我希望这能解决你的使用案例。

首先,非常感谢Aftnix! 你的榜样启动了我的自动数据包检查唤醒局域网项目。 我希望我的家庭服务器在闲置时进入休眠状态,但是一旦有请求进入,就会立即唤醒。这个想法是在ddwrt路由器上检查请求,确定它是一个合法请求并发送一个wol包。 对于SMTP来说,这个想法是排队多个数据包,让另一端满意一些假的响应,并在真实的服务器上蹦蹦跳跳。

我稍微修改了你的例子,排队了一个数据包,并发送下一个数据包。 这只是一个概念验证,但它工作正常。

 // struct and variable needed to store 1 packet struct packetbuffer { struct nfq_q_handle *qh; u_int32_t id; u_int32_t verdict; u_int32_t data_len; unsigned char *buf; }; struct packetbuffer pbuf; int counter = 0; static int cb (struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { uint32_t ip_src, ip_dst; struct in_addr s_ip; struct in_addr d_ip; uint16_t src_port; uint16_t dst_port; int verdict; int id; int ret; unsigned char *buffer; struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr (nfa); if (ph) { id = ntohl (ph->packet_id); printf ("received packet with id %d", id); } ret = nfq_get_payload (nfa, &buffer); ip_src = *((uint32_t *) (buffer + 12)); ip_dst = *((uint32_t *) (buffer + 16)); src_port = *((uint16_t *) (buffer + 20)); dst_port = *((uint16_t *) (buffer + 22)); s_ip.s_addr = (uint32_t) ip_src; d_ip.s_addr = (uint32_t) ip_dst; *(buffer + 26) = 0x00; *(buffer + 27) = 0x00; printf ( "source IP %s", inet_ntoa (s_ip)); printf ( "destination IP %s", inet_ntoa (d_ip)); printf ( "source port %d", src_port); printf ( "destination port %d", dst_port); if (ret) { switch (ph->hook) { case PREROUTING: printf ( "inbound packet"); //my_mangling_fun(); break; case OUTPUT: printf ( "outbound packet"); //my_mangling_fun(); break; } } // My modification starts here if ((counter % 2) == 0) { pbuf.qh = qh; pbuf.id = id; pbuf.data_len = ret; pbuf.buf = malloc(ret); memcpy(pbuf.buf, buffer, ret); printf(" queue package %d \n", id); } else { printf(" output 1st package %d, len=%d\n", pbuf.id, pbuf.data_len); verdict = nfq_set_verdict (pbuf.qh, pbuf.id, NF_ACCEPT, pbuf.data_len, pbuf.buf); free(pbuf.buf); printf(" output 2nd package %d, len=%d\n", id, ret); verdict = nfq_set_verdict (qh, id, NF_ACCEPT, ret, buffer); } counter++; return 0; } 

这不是所有的代码,但它应该是非常明显的变化。

我知道我晚了一点,但我决定发布这个解决方案,以备将来参考,希望有些批评者。

编辑:嗯也许我最好写一个像rinetd的用户空间进程。