在不使用环回网络的情况下将数据包转发到同一主机中的服

时间:2012-04-25 10:52:10

标签: c linux networking udp netfilter

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

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

#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);
}

请注意,在回调函数中,对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

这将从stdout中的netfilter_queue应用程序打印数据包。现在已经建立了开发环境,我们可以转向下一步。

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

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

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

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

那怎么办呢?

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

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

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

我听说应用程序可以通过编写一些文件将数据发送到同一主机中的服务器而不使用网络层(ip,端口)。我不知道这句话的有效性。但如果有人知道任何事情,那将是美好的。

我可能会因为过多的冗长而投票。但我认为这可以很有趣。我们可以一起找到解决方案:)

2 个答案:

答案 0 :(得分:1)

我提出以下解决方案:

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

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

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

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

答案 1 :(得分:1)

首先,非常感谢Aftnix!您的示例启动了我的自动数据包检查wake-on-lan项目。我希望我的家庭服务器在空闲时睡觉,但是一旦请求进入就会唤醒。想法是在ddwrt路由器上检查请求,确定它是合法请求并发送wol包。对于SMTP,我们的想法是对多个数据包进行排队,让另一端对某些虚假的响应感到高兴,并且可以透过实际的服务器进行操作。

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

// 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这样的用户空间进程。