пересылка пакетов на обслуживание на том же хосте без использования петлевой сети

У меня есть это приложение 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)

теперь мы можем скармливать мусорную полезную нагрузку udp этой штуке с помощью netcat как в режиме клиента, так и в режиме сервера

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

Это напечатает пакет из моего приложения netfilter_queue в стандартный вывод. Теперь, когда среда разработки настроена, мы можем перейти к следующему шагу.

Мы пытаемся достичь следующего:

Наш сервер слушает порт 5000. Теперь все входящие пакеты, предназначенные для порта udp 5000, будут поставлены ядром в очередь. И дескриптор этой очереди будет передан пользовательскому приложению, которое мы перечислили ранее. Этот механизм очереди работает следующим образом: когда пакет доступен, вызывается функция обратного вызова (cb() в нашем коде). после обработки функция обратного вызова вызывает nfq_set_verdict(). после того, как будет возвращен вердикт, следующий пакет выскочит из очереди. обратите внимание, что пакет не будет выталкиваться из очереди, если его предыдущему пакету не был выдан вердикт. Значения этого вердикта: NF_ACCEPT для принятия пакета, NF_DROP для отбрасывания пакета.

Что, если я хочу объединить полезные данные udp входящего и исходящего пакетов, не затрагивая клиентский и серверный код?

Если я хочу объединить полезные данные udp из нашего приложения в это самое приложение, нам нужно иметь под рукой несколько пакетов. Но мы видели, что пакет не выталкивается из очереди до того, как будет вынесен вердикт предыдущему пакету.

Итак, как это можно сделать?

Одним из возможных решений является выдача NF_DROP каждому пакету и сохранение этих пакетов в промежуточной структуре данных. Допустим, мы это сделали. Но как этот пакет может быть доставлен службе, прослушивающей порт 5000?

Мы не можем использовать сетевой стек для доставки пакета, потому что если мы это сделаем, то пакеты снова окажутся в NFQUEUE.

Другая проблема заключается в том, что сервер совершенно не зависит от этого приложения. Это означает, что он не должен видеть никакой разницы в пакетах. Он должен видеть пакеты, как будто они пришли от исходного клиента.

Я слышал, что приложение может отправлять данные на сервер на том же хосте без использования сетевого уровня (IP, порт), записывая некоторые файлы. Я не знаю достоверности этого утверждения. Но если кто-нибудь что-нибудь об этом знает, будет замечательно.

Я могу проголосовать за слишком многословие. Но я думаю, что это может быть забавная сессия. мы можем найти решение вместе :)


person Aftnix    schedule 25.04.2012    source источник


Ответы (2)


Предлагаю следующее решение:

  • сохранять пакеты в приложении и возвращать вердикт NF_DROP
  • повторно вводить пакеты в сетевой стек, используя сокеты RAW
  • пометить объединенные пакеты UDP с помощью DSCP (см. формат IP-пакетов)
  • в iptables добавьте правило для этого DSCP (--dscp) и ПРИНИМАЙТЕ пакет напрямую, без прохождения через ваше приложение netfilter

Если ваш провайдер уже помечает некоторые пакеты с помощью DSCP, вы можете добавить некоторые правила iptables, чтобы очистить их, например:

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

Надеюсь, это решит ваш вариант использования.

person user1202136    schedule 25.04.2012
comment
Спасибо, это похоже на то, что я пытаюсь сделать. Я сделаю несколько тестов по этому поводу, обновлю результаты. - person Aftnix; 25.04.2012
comment
Если я использую первый вариант, который вы указали (сохранение полезной нагрузки и заголовка каждого пакета, пойманного в пользовательской структуре, и помещение его в связанный список), то как мне повторно ввести пакет по моей потребности, с помощью nfq_set_verdict или с необработанными сокетами? - person elmazzun; 12.07.2015

Прежде всего, большое спасибо 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.

person Sjaaky    schedule 19.04.2013