在不使用环回网络的情况下将数据包转发到同一主机中的服务 [英] forwarding packets to service in same host without using loopback network

查看:14
本文介绍了在不使用环回网络的情况下将数据包转发到同一主机中的服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个 libnetfilter_queue 应用程序,它根据一些 iptables 规则从内核接收数据包.在直接解决我的问题之前,我提供了一个示例可行的代码和其他工具来设置测试环境,以便我们的问题定义和可能的解决方案可以更加准确和健壮.

I have this libnetfilter_queue application which receives packets from kernel based on some iptables rule. Before going straight to my problem, i'm giving a sample workable code and other tools to set up a test environment so that We problem definition and possible solutions can be more accurate and robust.

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

The following code describes the core functionality of the application:

#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 ('
', 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
");
  h = nfq_open ();
  if (!h)
    {
      fprintf (stderr, "error during nfq_open()
");
      exit (1);
    }

  printf ("unbinding existing nf_queue handler for AF_INET (if any)
");
  if (nfq_unbind_pf (h, AF_INET) < 0)
    {
      fprintf (stderr, "error during nfq_unbind_pf()
");
      exit (1);
    }

  printf ("binding nfnetlink_queue as nf_queue handler for AF_INET
");
  if (nfq_bind_pf (h, AF_INET) < 0)
    {
      fprintf (stderr, "error during nfq_bind_pf()
");
      exit (1);
    }

  printf ("binding this socket to queue '0'
");
  qh = nfq_create_queue (h, 0, &cb, NULL);
  if (!qh)
    {
      fprintf (stderr, "error during nfq_create_queue()
");
      exit (1);
    }

  printf ("setting copy_packet mode
");
  if (nfq_set_mode (qh, NFQNL_COPY_PACKET, 0xffff) < 0)
    {
      fprintf (stderr, "can't set packet_copy mode
");
      exit (1);
    }

  fd = nfq_fd (h);

  for (;;)
    {
      if ((rv = recv (fd, buf, sizeof (buf), 0)) >= 0)
    {
      printf ("pkt received
");
      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!
");
      continue;
    }
      perror ("recv failed");
      break;
    }

  printf ("unbinding from queue 0
");
  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
");
  nfq_unbind_pf (h, AF_INET);
#endif

  printf ("closing library handle
");
  nfq_close (h);

  exit (0);
}

注意回调函数中对 my_mangling_fun() 的两次调用被注释掉了.这是我处理传入和传出数据包的地方.我认为这段代码足以描述我的情况.如果需要进一步澄清,请询问,我将发布更多详细信息.

Notice in the callback function two calls to my_mangling_fun() is commented out. This is where i mangle the incoming and outgoing packet. I think this code would be sufficient to describe my case. If further clarification is need please ask, i will post further details.

可以说随附的 iptables 规则如下:

Lets say accompanying iptables rules are following :

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

让我们编译并启动 udp 的东西.

lets compile and fire udp the thing.

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

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

now we can feed garbage udp payload to this thing by netcat both client and server mode

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

这将在标准输出中打印来自我的 netfilter_queue 应用程序的数据包.现在开发环境已经设置好了,我们可以进行下一步了.

This will print the packet from my netfilter_queue app in stdout. Now that the development environment is set up, we can move to the next thing.

我们正在努力实现的目标如下:

What we are trying to achieve is following :

我们的服务器正在监听 5000 端口.现在所有发往 udp 端口​​ 5000 的传入数据包都将由内核排队.这个队列的句柄将交给我们之前列出的用户应用程序.这个队列机制是这样工作的:当一个数据包可用时,回调函数(我们代码中的 cb())被调用.处理后,回调函数调用nfq_set_verdict().在返回 verdict 后,下一个数据包将从队列中弹出.请注意,如果之前的数据包没有被发出判决,则该数据包不会从队列中弹出.这个判断值是 NF_ACCEPT 用于接受数据包,NF_DROP 用于丢弃数据包.

Our server is listening on 5000 port. Now all incoming packet destined to udp port 5000 will be queued by kernel. And the handle to this queue will be given to user application we listed earlier. This queue mechanism works like this: When a packet is available, the callback function(cb() in our code) is called. after processing, the callback function calls nfq_set_verdict(). after a verdict is returned, next packet will pop from the queue. notice that a packet will not pop from queue if its preceding packet has not been issued a verdict. This verdict values are NF_ACCEPT for accepting packet, NF_DROP for dropping the packet.

现在,如果我想在不触及客户端和服务器端代码的情况下连接传入和传出数据包的 udp 有效负载怎么办?

Now what if i want to concatenate the udp payloads of the incoming and outgoing packet without touching client and server side code?

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

If i want to concatenate udp payloads from our app this very app, then we need to have multiple packets at hand. But we have seen that a packet does not pops from queue before a verdict is issued to its preceding one.

那么如何做到这一点呢?

So how can this be done?

一种可能的解决方案是向每个数据包发出 NF_DROP 并将这些数据包保存在中间数据结构中.假设我们已经做到了.但是这个数据包如何才能传递到监听 5000 端口的服务呢?

One possible solution is issue a NF_DROP to every packet and save those packets in an intermediate data structure. Let's say we have done it. But how can this packet can be delivered to the service listening on 5000 port?

我们不能使用网络堆栈来传递数据包,因为如果我们这样做,那么数据包将再次进入 NFQUEUE.

We can't use network stack for delivering the packet, because if we do, then packets will end up in NFQUEUE again.

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

Another problem is, the server is totally agnostic about this app. That means it should not see any difference in the packets. It should see packets as if it came from the original client.

我听说一个应用程序可以通过编写一些文件,在不使用网络层(ip,端口)的情况下将数据发送到同一主机中的服务器.我不知道这个说法的有效性.但是,如果有人对此有所了解,那就太好了.

I have heard that a application can send data to a server in the same host without using network layer(ip,port) by writing some files. I do not know the validity of this statement. But if anyone knows anything about it , it will be wonderful.

我可能会因为过于冗长而被否决.但我认为这可能是一个有趣的会议.我们可以一起找到解决方案:)

I may get down voted for too much verbosity. But I think this can be fun session. we can find the solution together :)

推荐答案

我提出以下解决方案:

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

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

If your provider already tags some packets with DSCP, you can add some iptables rules to clear them, like:

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

我希望这可以解决您的用例.

I hope this solves your use-case.

这篇关于在不使用环回网络的情况下将数据包转发到同一主机中的服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆