2012-04-25 4 views
3

iptablesルールに基づいてカーネルからパケットを受け取るlibnetfilter_queueアプリケーションがあります。私の問題に直接進む前に、問題の定義と可能な解決策がより正確で堅牢になるように、実用的なサンプルコードとテスト環境を設定するためのツールを提供しています。 2つのコールがmy_mangling_funするコールバック関数でループバックネットワークを使用せずに同じホストのサービスにパケットを転送する

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

お知らせ()コメントアウトされている:

次のコードは、アプリケーションのコア機能を記載しています。これは、着信パケットと発信パケットをマングルするところです。私はこのコードが私の場合を説明するのに十分であると思う。さらなる明確化が必要な場合は、私は詳細を掲載します。

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

事UDPコンパイルして火災ができます:

は、添付のiptablesのルールは以下の通りとしましょう。

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

今はこれが標準出力で私のnetfilter_queueアプリからパケットを出力します

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

netcatをすることによって、この事にクライアントとサーバーの両方のモードをゴミUDPペイロードを養うことができます。開発環境が整ったので、次のものに移行することができます。

当社のサーバは5000ポートで待機している:私たちが達成しようとしている何

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

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

私はこのアプリからudpペイロードを連結したいと思うなら、複数のパケットを手元に置く必要があります。しかし、パケットがキューからポップしていないことがわかりました。

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

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

パケットを配信するためにネットワークスタックを使用することはできません。なぜなら、パケットがNFQUEUEに再び終わるからです。

もう1つの問題は、サーバーがこのアプリケーションに完全に無関心であることです。つまり、パケットに違いは見られないはずです。パケットは元のクライアントからのものであるかのように見えるはずです。

アプリケーションはネットワークレイヤ(ip、port)を使わずに同じホストのサーバにデータを送ることができると聞いています。私はこの声明の妥当性を知らない。しかし、誰かがそれについて何かを知っていれば、それはすばらしいことになります。

あまりにも冗長に投票してしまうことがあります。しかし、これは楽しいセッションだと思う。私たちは、私は以下のソリューションを提案:)一緒

答えて

1

を解決策を見つけることができます。アプリケーションに

  • 店パケットをとRAWソケット
  • を使用して、ネットワークスタックに評決NF_DROP
  • 再注入したパケットを返しますiptables内のDSCP(IPパケット形式を参照)
  • でタグ付けされたパケットをタグ付けし、このDSCP(--dscp)と一致するルールを追加し、ネットフィルタアプリケーションを通過することなくパケットを直接受け入れます。

あなたのプロバイダがすでにDSCPを持ついくつかのパケットをタグ付けした場合のように、あなたは、それらをクリアするために、いくつかのiptablesのルールを追加することができます。私は、これはあなたのユースケースを解決を願ってい

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

+0

ありがとう、これは私がやろうとしているように聞こえます。私はそれについていくつかのテストを行い、結果を更新します。 – Aftnix

+0

最初のオプション(カスタム構造体に捕捉された各パケットのペイロードとヘッダーを格納してリンクリストに入れる)を使用すると、必要に応じてnfq_set_verdictまたはrawソケットを使ってパケットを再注入する必要があります? – elmazzun

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

これはすべてのコードではありませんが、変更された内容はかなり明白です。

私はパーティーに少し遅れていることは知っていますが、私はこの解決策を投稿し、将来の参考にしたいと思っています。

編集:hmm多分、私はrinetdのようなユーザー空間のプロセスを書く方がよいでしょう。

+0

これはipv4でのみ動作することに注意してください。 – OneOfOne

関連する問題