Netfilter学習ノート

17552 ワード

この2、3日iptablesを勉强しています.これは本当にすごいと思います.2編のブログで紹介したいです.
第1編ではnetfilterとiptablesの関係、iptablesの原理を紹介します.第2編では,libipqおよびnfqueue-bindings-pythonを用いて,user状態がどのようにCおよびPythonを用いてiptablesのインタフェースを呼び出してパッケージを取得するかについて説明する.
netfilter
簡単に言えば、netfilterは、Linux Kernelに一連のhookを挿入し、kernelが異なるレイヤのネットワークスタックにコールバック関数を登録することを許可する一連のコンピュータネットワークスタックのフレームワークであり、これらのコールバック関数は、パケットが対応するhookに入るときに呼び出される.
ネットワークスタック内のnetfilterのサポートを図に示します.
リンク層とネットワーク層においてパケットフローをカプセル化する経路に従って5種類のhooks:prerouting,input,forward,output,postroutingが見られる.この5つのhooksは、パケットが到着すると、パケットフローの順序に従って対応するコールバック関数を呼び出し、4つのタイプのテーブルのchain(iptablesで説明される)をフィルタリングし、修正します.filter、nat、mangle、rawです.これらのフィルタを定義するルールは、1つのユーザ状態のコマンドiptablesによって構成されます.つまり、私が次に詳細に説明するコマンドです.
iptables
ここには6つのシリーズの文章がiptablesを紹介しています.はっきり言っていて、入門学習に適しています.
iptablesには4つのタイプのテーブルがあります.filter、nat、mangle、rawです.ここではfilterテーブルについて説明します.
filterテーブルは主にパッケージをフィルタリングするために使用され、このテーブルにはデフォルトのchain:INPUT,FOrWARD,OUTPUTの3つがあります.
chainは何をしていますか?(ここから)
chainとは、すべての外部パケットの侵入を防止するルールをINPUT chainに追加することができるパケットフィルタリングルールのセットです.OUTPUT chainに、ユーザーがWebサーバーに接続しないようにする方法を追加できます.ネットワークへのアクセスを準備するパケットは、chain内のフィルタリングルールに従って検証され、パケットがルールに合致しない場合は、INPUTとOUTPUTはLinux kernelでACCEPTとして予め設定され、FOrWARDはDROPとして予め設定されているため、ネットワーク内に直接アクセスします.
たとえば、あるアドレス(192.168.1.2など)に送信されたパケットを破棄する場合は、次のようにします.
$ sudo iptables -A OUTPUT -d 192.168.1.2 -j DROP

ここで-AはOUTPUTというチェーンのルールを修正することを指し、-dはこのパッケージのdestinationを表し、-jの後にtargetを加え、ACCEPT(何もせずにパケットを流す)、DROP(パケットを破棄する)、QUEUE(パケットをキューに挿入し、ユーザ状態処理に渡す、第2編で詳細に説明する)、RETURN(このchainから直接戻り、前のchainの次のルールに進む)、および自己定義のCHAINの5つの選択肢があります.
$ sudo iptables -N SELF_CHAIN

すなわち,異なるソースアドレス,ターゲットアドレス,ポート,プロトコルなどの規則に従って分類し,異なるchainで処理することができ,フィルタ条件をより合理的に管理することができる.
また、いくつかの一般的なオプションについて説明します.
-A chain                # modify a chain
-D chain rulenum        # delete a specific rule of chain
-I chain rulenum rule   # insert rule in rulenum of chain
-R chain rulenum rule   # replace rule with rulenum of chain
-L [chain]              # list rules of chain
-F [chain]              # flush the rules of chain
-N chain                # new a chain
-X [chain]              # delete chain

-p protocol             # e.g., tcp, udp, icmp...
-s source address       # e.g., 192.168.1.22
-d destination address  # e.g., 192.168.1.13
-j target               # e.g., ACCEPT, DROP, RETURN, QUEUE, other-chain
-i in-interface         # e.g., eth0
-o out-interface        # e.g., eth0

次の記事では、iptablesのtargetがQUEUEまたはNFQUEUEである場合、ユーザ状態が関連インタフェースをどのように呼び出すかについて主に説明します.
前にnetfilterとiptablesの簡単な原理と使い方について話しましたが、iptablesにとって、パッケージをDROP、ACCEPT操作しかできないと弱いように見えますが、実はiptablesの中でfilterテーブルの中で一番すごいのはQUEUE(NFQUEUE)というtargetです.
では、iptablesがQUEUEにパッケージを挿入すると、queueのデータを読み取るにはどのような方法がありますか?
ここでは、C言語で使用されるlibipqとpythonで使用されるnfqueueの2つの方法を紹介します.
libipq libipqは開発者に提供されたiptables queueを読み取るためのCライブラリです.具体的な使い方はlinux man pageとここの使い方を参照してください.注意しなければならないのは、私のマシンに3つのヘッダファイルを追加しなければなりません.
#include 
#include 
#include 

またコンパイルされたファイルはsudoで実行しなければなりません!!
ここでは主にデータ構造を用いた
1
2
3
struct ipq_handle *h;

h = ipq_create_handle(0, PF_INET);

さらに、IPQ_COPY_PACKET、すなわちqueue内のパッケージのpayloadをヘッダとともにユーザ空間にコピーするモードを設定する必要がある.
1
status = ipq_set_mode(h, IPQ_COPY_PACKET, BUFSIZE);

その後のパス:
1
status = ipq_read(h, buf, BUFSIZE, 0);

queueuのパッケージをユーザースペースに1つずつコピーし、ユーザーが操作し、ユーザーは次のことを実行できます.
1
ipq_message_type(buf)

得られるパケットのタイプは、NLMSG_ERRORであってもよいし、IPQM_PACKETであってもよい.後者であれば、通常のパケットであり、以下のようなコードによってパケットを操作することができる.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
case IPQM_PACKET: {
  ipq_packet_msg_t *m = ipq_get_packet(buf);

  struct iphdr *ip = (struct iphdr*) m->payload;

  struct tcphdr *tcp = (struct tcphdr*) (m->payload + (4 * ip->ihl));

  int port = htons(tcp->dest);         

  status = ipq_set_verdict(h, m->packet_id, NF_ACCEPT, 0, NULL);
  if (status < 0)
      die(h);
      break;
  }
}

上段のコードは、bufからパッケージm全体を取得した後、struct iphdrおよびstruct tcphdrを介してipパッケージヘッダおよびtcpパッケージヘッダを取得することができ、最後に最も重要なコードがあることを意味する.
1
status = ipq_set_verdict(h, m->packet_id, NF_ACCEPT, 0, NULL);

ここでの意味は,従来のルールではtargetをACCEPTとすることに相当し,もちろんNF_DROPなどとしてもよい.
ここで疑問なのは、ここでstruct tcphdrを獲得した以外に、TCPのpayloadに関連する構造が見つからず、どのように獲得すればいいか分からないことです.
nfqueue
Cのlibipqに比べてpythonをサポートするnfqueueは強く見えますが、特にscapyと組み合わせて使う場合です.
まず、iptablesにおけるtargetは、前述の5つの項目(ACCEPT,DROP,RETURN,QUEUE,other_chain)に加えて、QUEUEの拡張であるNFQUEUEと呼ばれる.QUEUEと比較して、ユーザによって異なるqueue numberを指定することができる.
nfqueueを使用する前に、次のパッケージをインストールする必要があります.
$ sudo aptitude install libnetfilter-queue-dev
$ sudo aptitude install nfqueue-bindings-python
$ sudo aptitude install python-scapy

その後pythonを用いてNFQUEUEを操作することができるようになった.
ホストA(192.168.1.1)からホストB(192.168.1.2)にパケットを転送する場合、パケットを解析する必要があり、TCPプロトコルのパケットであり、そのflagsがACK|PSHである場合、そのpayloadを修正する(例えば「hack」に置き換える):
まず、ホストAでiptablesを操作する必要があります.
$ sudo iptables -A OUTPUT -d 192.168.1.2 -p tcp -j NFQUEUE

次に、次のコードを使用します.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import os,sys,nfqueue,socket
from scapy.all import *

def ch_payload_and_send(pkt):
  pkt[TCP].payload == "hack"
  send(pkt, verbose=0)

def process(i, payload):
  data = payload.get_data()
  pkt = IP(data)

  # Check if TCP flags is ACK|PSH
  if pkt[TCP].flags == 24:
      # Dropping the packet
      payload.set_verdict(nfqueue.NF_DROP)
      ch_payload_and_send(pkt)
  else:
      # Accepting the packet
      payload.set_verdict(nfqueue.NF_ACCEPT)
  
def main():
  q = nfqueue.queue()
  q.open()
  q.unbind(socket.AF_INET)
  q.bind(socket.AF_INET)
  q.set_callback(process)
  q.create_queue(0)

  try:
      q.try_run()
  except KeyboardInterrupt:
      print "Exiting..."
      q.unbind(socket.AF_INET)
      q.close()
      sys.exit(1)

main()

ここでは、scapyという非常に強力なモジュールを使用しています.IP()TCP()などを通じて直接パッケージを説明し、操作することができ、非常に便利で、具体的にはドキュメントを参照することができます.ここでは、インストール方法について説明します.
$ wget scapy.net
$ mv index.html scapy-latest.zip
$ chmod +x scapy-latest.zip
$ mv scapy-latest.zip /usr/local/bin/scapy

次のように実行できます.
$ sudo scapy

scapyのインタラクティブモードを直接オンにしました.
原文の出典:http://ytliu.github.io/blog/archives/中の文章は全部いいですね.