3

いくつかのiptablesルールに基づいてカーネルからパケットを受信するこのlibnetfilter_queueアプリケーションがあります。私の問題に直接進む前に、テスト環境をセットアップするためのサンプルの実行可能なコードと他のツールを提供します。これにより、問題の定義と可能な解決策がより正確で堅牢になります。

次のコードは、アプリケーションのコア機能を説明しています。

#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()への2つの呼び出しがコメント化されていることに注意してください。これは、着信パケットと発信パケットをマングルする場所です。このコードは私のケースを説明するのに十分だと思います。さらに詳しい説明が必要な場合は、お問い合わせください。詳細を投稿します。

付随する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アプリからのパケットがstdoutに出力されます。開発環境が整ったので、次のことに移ります。

私たちが達成しようとしていることは次のとおりです。

サーバーは5000ポートでリッスンしています。これで、udpポート5000宛てのすべての着信パケットがカーネルによってキューに入れられます。そして、このキューへのハンドルは、前にリストしたユーザーアプリケーションに与えられます。このキューメカニズムは次のように機能します。パケットが使用可能になると、コールバック関数(コードではcb())が呼び出されます。処理後、コールバック関数はnfq_set_verdict()を呼び出します。判定が返された後、次のパケットがキューからポップされます。前のパケットに判定が発行されていない場合、パケットはキューからポップされないことに注意してください。この判定値は、パケットを受け入れる場合はNF_ACCEPT、パケットをドロップする場合はNF_DROPです。

クライアント側とサーバー側のコードに触れることなく、着信パケットと発信パケットのudpペイロードを連結したい場合はどうすればよいですか?

このアプリのアプリからudpペイロードを連結する場合は、複数のパケットを手元に用意する必要があります。しかし、前のパケットに判定が発行される前に、パケットがキューからポップされないことがわかりました。

では、これはどのように行うことができますか?

考えられる解決策の1つは、すべてのパケットにNF_DROPを発行し、それらのパケットを中間データ構造に保存することです。私たちがそれをしたとしましょう。しかし、このパケットを5000ポートでリッスンしているサービスに配信するにはどうすればよいでしょうか。

パケットの配信にネットワークスタックを使用することはできません。使用すると、パケットは再びNFQUEUEに送られます。

もう1つの問題は、サーバーがこのアプリを完全に認識していないことです。つまり、パケットに違いは見られないはずです。元のクライアントから送信されたかのようにパケットが表示されます。

アプリケーションは、いくつかのファイルを書き込むことにより、ネットワーク層(ip、port)を使用せずに、同じホスト内のサーバーにデータを送信できると聞きました。私はこの声明の妥当性を知りません。しかし、誰かがそれについて何か知っているなら、それは素晴らしいでしょう。

冗長すぎるために投票が下がる可能性があります。でも、これは楽しいセッションになると思います。私たちは一緒に解決策を見つけることができます:)

4

2 に答える 2

1

私は次の解決策を提案します:

  • アプリケーションにパケットを保存し、判定NF_DROPを返します
  • RAWソケットを使用してネットワークスタックにパケットを再注入します
  • 連結されたUDPパケットをDSCPでタグ付けします(IPパケット形式を参照)。
  • iptablesで、このDSCP(--dscp)に一致するルールを追加し、netfilterアプリケーションを通過せずにパケットを直接受け入れます。

プロバイダーがすでにいくつかのパケットにDSCPのタグを付けている場合は、次のように、いくつかのiptablesルールを追加してそれらをクリアできます。

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

これでユースケースが解決することを願っています。

于 2012-04-25T11:15:10.063 に答える
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のようなユーザースペースプロセスを書いたほうがいいでしょう。

于 2013-04-19T13:34:19.357 に答える